From 99017554d5c1c0f3098497f0b2558e00b4ad1745 Mon Sep 17 00:00:00 2001 From: Continuum Constantinus Date: Wed, 5 Feb 2025 08:48:16 -0300 Subject: [PATCH] Let there be light. We illustrate how continuations are essential to web programming and how applications on top of web servers are much harder to write due to the stateless nature of HTTP. Continuations turn web programs as easy to write as desktop applications. --- .gitignore | 11 + Makefile | 17 + continuations.nw | 539 +++++++++++++++++++++++++ continuations.rkt | 59 +++ noweb.sty | 989 ++++++++++++++++++++++++++++++++++++++++++++++ peat | 224 +++++++++++ 6 files changed, 1839 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 continuations.nw create mode 100644 continuations.rkt create mode 100644 noweb.sty create mode 100644 peat diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0efcf5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*~ +continuations +*.aux +*. +*.fdb_latexmk +*.fls +*.log +*.out +*.pdf +*.tex +tmp/* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..24e7400 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +SHELL=/bin/sh + +default: \ +continuations.rkt continuations.pdf + +livedoc: + echo continuations.nw | python peat -C 'make continuations.pdf' + +continuations.tex: continuations.nw + any weave -delay -index continuations.nw > continuations.tex + +continuations.pdf: continuations.tex + latexmk -pdf continuations.tex && pgrep mupdf && kill -HUP $$(pgrep mupdf) + +continuations.rkt: continuations.nw + (any tangle -Rcontinuations.rkt < continuations.nw > continuations.rkt || \ + (rm continuations.rkt && exit 1)) diff --git a/continuations.nw b/continuations.nw new file mode 100644 index 0000000..08d7a9e --- /dev/null +++ b/continuations.nw @@ -0,0 +1,539 @@ +% -*- noweb-code-mode: racket-mode; -*- +\documentclass[a4paper,11pt]{article} +\usepackage[text={6.5in,10in},centering]{geometry} + +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage{csquotes} +\usepackage[english]{babel} + +\usepackage{etoolbox} +\AtBeginEnvironment{quote}{\small} +\AtBeginEnvironment{verbatim}{\small} +\usepackage{float} + +%% \usepackage[backend=biber]{biblatex} +%% \addbibresource{refs.bib} +%% \renewcommand{\cite}{\parencite} +\usepackage[hyperref]{xcolor} +\definecolor{darkblue}{rgb}{0,0,0.4} +\usepackage[colorlinks=true,linkcolor=darkblue,citecolor=]{hyperref} +\usepackage{amsmath,amsthm,amssymb} +\allowdisplaybreaks +\usepackage{lmodern} +\usepackage{noweb} +\noweboptions{smallcode} %longchunks,longxref +% redefine this command so we can allow code to break across +% multiple pages +%% \def\nwendcode{\endtrivlist \endgroup \vfil\penalty10\vfilneg} +%% \let\nwdocspar=\smallbreak +\def\nwendcode{\endtrivlist \endgroup} +\let\nwdocspar=\par + +\usepackage{booktabs} + +\title{On the difficulty of writing\\ +multiple-step processes in web applications} +%\author{Daniel Bastos} +\date{May 2019} % May 2020 is the real date +\begin{document} +\fontfamily{cmr}\selectfont +\maketitle +%\setlength{\parskip}{3pt} +%\setlength{\parindent}{0pt} + +\section*{The problem} + +Writing backend applications usually involves the implementation of +some kind of memory in the system so that it can take the user from +one step to another until a process is completed. Shopping carts, for +example; the system has to memorize the items until the user decides +to check out. The process goes through various steps such as +specifying shipping choices, filling out an address for delivery, +typing out payment information {\em et cetera}. For instance, the +tiny application at the address +% +\vskip0.3cm\centerline{\tt https://continuations.antartida.xyz/}\vskip0.3cm +% +\noindent implements a three-step application. At each step you type +a number and you get the sum at the last one. I would expect the code +of such application to be described by a procedure such as: + +\begin{verbatim} +(define (the-sum-application) + (define m (get-number "First")) + (define n (get-number "Second")) + (show-the-sum-of m n)) +\end{verbatim} + +\noindent This would definitely work in a console application or a +desktop application. But doing this on the web takes a bit of power +from your progamming language. The procedure {\tt get-number} will +have to show an HTML form and wait for an answer from the user. The +web server itself can't wait for only a single user: it has be always +ready to serve other requests, even from the same user who just +received an HTML form asking for a number because, of course, this +user could open a new browser and talk to the web server in parallel. + +\section*{Inelegant solutions} + +Inelegant solutions are many. You could use session cookies so that +users carry their own data request after request. You could save the +data in a server session so that the server carries the temporary +information for each user. You could even use your own permanent +storage to save temporary information. + +Apparently these solutions are not so bad: you need to memorize +information and you use some of your resources for that. There is +apparently nothing wrong with that. + +\section*{The hard life of web servers} + +Say the user is ready to pay for the products. Let's take name and +address first, then we take shipping preferences and only then we +collect payment information. To take name and address, we design the +entry point {\tt /step0}. Let's call it {\tt /step0} because that's +where the data collection begins. This point is easy to implement +because the system doesn't have to check for anything. It displays an +empty form asking for name, address {\em et cetera}. Submission takes +the user to the entry point {\tt /step1}. + +Now trouble begins. The point {\tt /step1} is another entry point of +the application. Users might come to this entry point without passing +through {\tt /step0}. For instance, say a user goes through {\tt + /step0}, lands on {\tt /step1} and bookmarks the URL for continuing +later. When the user is back at {\tt /step1}, the system has to +recover the information given in {\tt /step0} and let the user +continue. Or, if the information has expired, the system must +gracefully handle the fact that the information that {\tt /step1} +requires is no longer available. + +What happens to the business logic? Imagine you are the web server. +A user hits the entry point {\tt /step1}. What do you do? You have +to answer many questions. Do I know this user? Has the user given me +all the information from {\tt /step0}? Where can I find it? The only +way to answer these questions is by writing more code. Once you write +the code to handle {\tt /step1} properly, the user goes to {\tt + /step2}. At {\tt /step2}, the same questions appear and a little +more. Do I know this user? Has the user given me all the information +from {\tt /step0}? How about {\tt /step1}? An attacker could easily +provide information for satisfying {\tt /step1} leaving {\tt /step0} +unsatisfied. You have to check for everything.%% Say you do all of +%% that. If the user at {\tt /step1} goes back to {\tt /step0}, what do +%% you do? Do you pretend the user hasn't been at all at {\tt /step0}? +%% That would be the easier solution, but it's foolish: the user was +%% there moments ago. You could at least recognize the fact, even if you +%% let the user redo the step, perhaps for fixing some information has +%% was entered incorrectly. + +This illustrates a considerable difference between writing web +applications and desktop applications: a web application has too many +entry points; a web server meets users completely out of the blue. It +has no easy way to {\bf force} users to respect a step by step +process. All by itself it has to discover where users are in the +conversation and continue the conversation from that point on. + +This doesn't happen to a desktop application. Consider a PDF viewer. +The user might expect to start the program and have all previously +opened PDF documents to be reloaded on the exact page the user left +them off. The viewer has annotated all of that somewhere and it's a +matter of putting everything back in place. That's no problem. +Anything the user does is carefully watched by the program until it is +shut down. As another example, consider a chat server that receives +thousands of different users at a single time. Clients must respect a +protocol: when a client connects it must tell his nickname, password, +which channel to join. The state of each client is always +well-defined in the server. If the client drops the connection, the +server knows about it and so it knows the client has gone back to its +step 0. The web is different: browsers begin a complex conversation +with the web server but, in between each request, browsers can +silently go away and suddenly come back expecting to carry on that +same old conversation. + +\section*{{\em Continuations} can help} + +If you haven't heard of {\em continuations}, don't worry. We will see +what it lets us do and it can serve as one first example of its +powers. (Technical note: if you care about terminology, we will be +using {\em first-class continuations} from a functional programming +language called {\em Racket}, a language from the Lisp family of +languages.) + +Suppose it would be possible to somehow keep all the state of the +conversation with the browser in some automatic way, that is, suppose +we have a solution that can {\em suspend} the entire world of a user +and automatically recover that world when the user is back to continue +the interaction. We will use a procedure called {\tt send/suspend}. +This procedure has the ability to send an HTML page to a browser and +save the web server's state precisely {\em after} sending the page. +If the user continues the process by, say, submitting a form contained +in the HTML page, the web server will continue precisely from where it +left off, effectively letting the program write the code as if the +user answers {\em immediately} each step of a multiple-step process. + +Let's design a tiny application with {\tt send/suspend}. + +For clarity, say the application will get two numbers from a user and +provide the sum of these numbers. On the first step, it gets one +number; on the second, another; on the final step, it shows the sum. +So this is a multiple-step application, like checking out a shopping +cart. It's tiny but it's just as difficult to write. + +\renewcommand{\lshift}{{\tt\textless{}\textless{}}} +\renewcommand{\rshift}{{\tt\textgreater{}\textgreater{}}} + +In what follows, we use a Lisp language to scheme up the application +in high precision. Don't worry if you don't know any Lisp. This is +the type of code you can read with your hands in your pockets. In a +language like Javascript, to give an input $x$ to a function $f$, we +write [[f(x)]]. In Lisp, we write [[(f x)]]. When we define the +procedure [[f]], we also write its definition in the same way we call +it: [[(define (f x) ...)]]. So, the following chunk of code defines a +procedure called [[the-sum-application]]. It consumes an HTTP +[[request]] and produces an HTML page. + +<>= +(define (the-sum-application request) + (define m (get-number "First")) + (define n (get-number "Second")) + (response/xexpr + (make-page-showing-the-sum-of m n))) +@ %def the-sum-application + +The hard work in this application is definitely in [[get-number]]. It +has to somehow display an HTML page to the user and only return when +it somehow gets a number back. + +\subsection*{Getting a number from the user} + +To get a number we need some HTML. + +\begin{verbatim} + + [...] + +

The sum application

+
First:
+ + +
+ + +\end{verbatim} + +Suppose we have a procedure [[form-ask-number]] which produces a +representation of this HTML. + +\begin{verbatim} +racket@continuations.rkt> (form-ask-number "First") +'((form + ((action [...]) (method "get")) + "First" + ":" + (br) + (input ((type "text") (name "number") (value ""))) + (input ((type "submit") (value "Go!"))))) +\end{verbatim} + +Also, suppose we have a procedure [[make-page]] which is able to embed +a form like that into a complete HTML page. + +\begin{verbatim} +racket@continuations.rkt> (make-page (form-ask-number "First")) +'(html + (head (title "The sum application")) + (body + (h1 "The sum application") + (form + ((action [...]) (method "get")) + "First" + ":" + (br) + (input ((type "text") (name "number") (value ""))) + (input ((type "submit") (value "Go!")))))) +\end{verbatim} + +Don't worry about these intermediate HTML representations: we also +have procedures that can quickly turn them into a perfect HTML string +that browsers can read. + +\begin{verbatim} +racket@continuations.rkt> (xexpr->string (make-page '((p "Hello")))) +"[...][...]

Hello

" +\end{verbatim} + +\subsection*{The specification of {\tt send/suspend}} + +The procedure [[send/suspend]] is capable of sending an HTML page to a +browser and suspending the system until the user submits back the form +contained in the HTML page. Let's properly specify this procedure. + +By the definition of the procedure [[send/suspend]], it takes a +procedure as its argument. For concreteness, let's call this +procedure-argument as [[html-page-asking-number]]. If you call this +function as in [[(html-page-asking-number ...)]], you get an HTML page +with a form asking for a number. It is [[send/suspend]] that will +invoke the procedure [[(html-page-asking-number ...)]] and, by design, +{\tt send/suspend} will give your procedure a URL: imagine your +procedure will be called as in [[(html-page-asking-number url)]]. +Let's have a look at a sample form that [[html-page-asking-number]] +produces. + +\begin{verbatim} +
+ First:
+ + +
+\end{verbatim} + +By design, the web server pairs the URL +% +\vskip0.3cm +\centerline{[[/;((%22k%22%20.%20%22(25%201%2039206621)%22]]} +\vskip0.3cm +% +\noindent with the state of the system at the moment the form was +generated. It does that before sending the form to the browser. It +then sends the form to the browser. When the user requests this URL, +the web server will load back the state it was right after sending the +form to the user's browser. With the ability to do this, +[[get-number]] can be straightforwardly put in the following words: + +\begin{verbatim} +(define (html-page-asking-first-number url) + (response/xexpr (make-page (form-ask-number "First" url)))) + +(define (get-number) + (define response (send/suspend html-page-asking-number)) + (extract-number 'number response)) +\end{verbatim} + +The time from the beginning of execution of [[send/suspend]] to its +return might be an entire day if the user only submitted the form on +the next day, but the system will think [[send/suspend]] returned +immediately because it continues exactly where it left off after +sending the page to the user. (If you're ``suspended'', you don't +notice that time has passed.) We can then extract the number from the +response that just arrived\footnote{In Lisp, a function always returns + whatever is the last expression in the function's body. You don't + have to say [[(return (extract-number ...))]] as is typical in + popular languages.}. + +If we decide to add error treatment, we need to insist with the user +to give us a number. We achieve this repetition by writing a loop of +the following sort. Yes, this is more complex than we had planned, +but, if you look carefully, you see there is only an [[if]] statement +added and now we will not crash trying to add things that are not +numbers. + +<>= +(define (get-number label [error #f]) + (define (html-page-asking-number url) + (response/xexpr (make-page (form-ask-number label url error)))) + (define response (send/suspend html-page-asking-number)) + (define n (extract-number 'number response)) + (if (number? n) + n + (get-number label "Omg, you don't know how to write a number?"))) +@ %def get-number + +Why did we define [[html-page-asking-number]] inside [[get-number]]? +We want the procedure [[html-page-asking-number]] to access the +[[label]] argument of [[get-number]]. + +Also notice that if [[n]] is not a number, then we ask again for a +number showing an error message, otherwise [[get-number]] returns the +number [[n]]. The syntax is +% +\begin{verbatim} + (if test-expression + case-expression-is-true + case-otherwise) +\end{verbatim} +% +\noindent The repetition takes place by recursively invoking +[[get-number]]. That's the loop. + +Let's get back to the [[send/suspend]] talk. By assuming +[[send/suspend]] returns immediately, the information we need for the +rest of the application is immediately available right on the next +line of the code. This means that, effectively, there is no second +point of entry to the application, so the problems generated by the +inelegant solutions don't arise. If the user bookmarks the action URL +and visits it some time later when it has expired, the web server +would report the page has expired, forcing the user to restart. (We +can also store these URLs forever; users can come back ten years later +and continue.) + +This procedure is used twice in [[the-sum-application]] procedure, +since we get two numbers. Showing the sum is just a matter of +building another page. + +\subsection*{Showing the sum} + +When the user is done with providing the second number, the variables +[[m]] and [[n]] hold the numbers, so the final step is just building a +page that displays the sum. You know how that is done. + +<>= +(define (make-page-showing-the-sum-of m n) + (make-page + `((p ,(format "~a + ~a = ~a" m n (+ (inexact->exact m) (inexact->exact n)))) + (p ,@(button-to "/" "Do it again!"))))) +@ %def make-page-showing-the-sum-of + +That's it. + +\section*{How is {\tt send/suspend} possible?} + +The feasibility of [[send/suspend]] is given by {\em continuations}, +an abstract representation of the control state of a program. It's a +snapshot of the program itself. Think of it as a data structure that +can hold the entire state of the program. If your programming +language offers such data structure as a first-class value, that means +you can {\tt send/suspend} as designed in this document and much more. + +Our main objective was to show how powerful continuations are. +Implementing {\tt send/suspend} would show how it is possible, but it +would break our promise that you can keep your hands in your pockets. +The topic is pretty difficult. + +\section*{A note about Javascript web applications} + +Another solution in multiple-step processes on the web is to use +client-side technology. You can collect up all the answers from a +user without ever talking to the backend and, at the final step, send +all the information to the backend, finishing the process. This +approach is clearly a solution today given that browsers are so +powerful, but in practice, however, a mix of the two approaches is +usually required. For example, consider an API server whose clients +are backend systems; there is no advanced browser there to come to the +rescue when it comes to implement multiple-step processes. + +%% \section*{Still...} + +%% Still, life on the web is hard anyhow. We have partially described +%% and partially solved one difficulty that programmers go through. +%% There are many more. Web applications are exposed to the whole world, +%% full of malicious attackers. A naïve prototype has no chance against +%% the average resourceful attacker. Perhaps your web server will handle +%% many more users than it was designed to. What about user interface? +%% The web interface is not quite responsive in the sense of how fast a +%% graphical user control reacts. There are fantastic libraries to +%% precisely solve such problems. Have you used them? Using these +%% libraries is a problem in its own right. + +%% Life on the web is not easy. We could build the case in which the web +%% is a definite failure. Not the ocean of information that it is, but +%% the ship in which we navigate it. Intelligence in design loses many +%% battles throughout history, but it always eventually counterattacks +%% with a final blow, knocking its opponents down for good. The network +%% itself is the Internet, a pretty successful project. It has allowed +%% so much diversity. Diversity is security. Now look at the browser. +%% It is so complex and so large, only few very big players can give it a +%% shot. There is virtually no diversity. It's bound to fail. + +%% \section*{How [[send/suspend]] is possible} + +%% Show continuations. Then implement send/suspend as Systems +%% Programming did. + +%% Next, we generate a new dispatch tag, and we record the mapping from +%% the tag to k: + +%% (define (send/suspend mk-page) +%% (let/cc k +%% (define tag (format "k~a" (current-inexact-milliseconds))) +%% (hash-set! dispatch-table tag k) +%% ...)) + +%% Finally, we abort the current computation, supplying instead the page +%% that is built by applying the given mk-page to a URL for the generated +%% tag: + +%% (define (send/suspend mk-page) +%% (let/cc k +%% (define tag (format "k~a" (current-inexact-milliseconds))) +%% (hash-set! dispatch-table tag k) +%% (abort (mk-page (string-append "/" tag))))) + +%% \section*{That's it} + +%% The rest of this document is the rest of the implementation of the +%% application. It involves every little detail that a machine needs to +%% get the job done. + +\section*{Chunks} +\nowebchunks +\section*{Index} +\nowebindex + +\section*{The rest of the source code of the application} + +<>= +(define (make-page content) + (define title "The sum application") + `(html + (head (title ,title)) + (body (h1 ,title) + ,@content))) +@ %def make-page + +<>= +(define (form-ask-number label action [error #f]) + `((form ([action ,action] [method "get"]) + ,label ":" (br) + (input ([type "text"] [name "number"] [value ""])) + ,@(if error `((br) (font ((color "#ff0000")) ,error) (br)) '()) + (input ([type "submit"] [value "Go!"]))))) +@ %def form-ask-number + +<>= +(define (button-to url text) + `((form ([action ,url] [method "get"]) + (input ([type "submit"] [value ,text]))))) +@ %def button-to + +<>= +<> +<> +<> +@ + +\subsection*{The code in the order seen by the compiler} +<>= +#lang racket/base + +(require web-server/servlet + web-server/servlet-env + web-server/http/redirect + xml) + +<> +<> +<> +<> + +(define (main) + (define port 1025) + (displayln (format "Serving on localhost:~a" port)) + (serve/servlet the-sum-application + #:port port + #:listen-ip #f + #:command-line? #t + #:servlet-path "" + #:servlet-regexp #rx"")) + +(module+ main + (main)) + +(define (extract-number id response) + (string->number + (bytes->string/utf-8 + (binding:form-value + (bindings-assq (string->bytes/utf-8 (symbol->string id)) + (request-bindings/raw response)))))) +@ +\end{document} + diff --git a/continuations.rkt b/continuations.rkt new file mode 100644 index 0000000..ddf2a05 --- /dev/null +++ b/continuations.rkt @@ -0,0 +1,59 @@ +#lang racket/base + +(require web-server/servlet + web-server/servlet-env + web-server/http/redirect + xml) + +(define (the-sum-application request) + (define m (get-number "First")) + (define n (get-number "Second")) + (response/xexpr + (make-page-showing-the-sum-of m n))) +(define (get-number label [error #f]) + (define (html-page-asking-number url) + (response/xexpr (make-page (form-ask-number label url error)))) + (define response (send/suspend html-page-asking-number)) + (define n (extract-number 'number response)) + (if (number? n) + n + (get-number label "Omg, you don't know how to write a number?"))) +(define (make-page-showing-the-sum-of m n) + (make-page + `((p ,(format "~a + ~a = ~a" m n (+ (inexact->exact m) (inexact->exact n)))) + (p ,@(button-to "/" "Do it again!"))))) +(define (make-page content) + (define title "The sum application") + `(html + (head (title ,title)) + (body (h1 ,title) + ,@content))) +(define (form-ask-number label action [error #f]) + `((form ([action ,action] [method "get"]) + ,label ":" (br) + (input ([type "text"] [name "number"] [value ""])) + ,@(if error `((br) (font ((color "#ff0000")) ,error) (br)) '()) + (input ([type "submit"] [value "Go!"]))))) +(define (button-to url text) + `((form ([action ,url] [method "get"]) + (input ([type "submit"] [value ,text]))))) + +(define (main) + (define port 1025) + (displayln (format "Serving on localhost:~a" port)) + (serve/servlet the-sum-application + #:port port + #:listen-ip #f + #:command-line? #t + #:servlet-path "" + #:servlet-regexp #rx"")) + +(module+ main + (main)) + +(define (extract-number id response) + (string->number + (bytes->string/utf-8 + (binding:form-value + (bindings-assq (string->bytes/utf-8 (symbol->string id)) + (request-bindings/raw response)))))) diff --git a/noweb.sty b/noweb.sty new file mode 100644 index 0000000..1ea980b --- /dev/null +++ b/noweb.sty @@ -0,0 +1,989 @@ +% noweb.sty -- LaTeX support for noweb +% DON'T read or edit this file! Use ...noweb-source/tex/support.nw instead. +{\obeyspaces\AtBeginDocument{\global\let =\ }} % from texbook, p 381 +\def\nwopt@nomargintag{\let\nwmargintag=\@gobble} +\def\nwopt@margintag{% + \def\nwmargintag##1{\leavevmode\llap{##1\kern\nwmarginglue\kern\codemargin}}} +\def\nwopt@margintag{% + \def\nwmargintag##1{\leavevmode\kern-\codemargin\nwthemargintag{##1}\kern\codemargin}} +\def\nwthemargintag#1{\llap{#1\kern\nwmarginglue}} +\nwopt@margintag +\newdimen\nwmarginglue +\nwmarginglue=0.3in +\def\nwtagstyle{\footnotesize\Rm} +% make \hsize in code sufficient for 88 columns +\setbox0=\hbox{\tt m} +\newdimen\codehsize +\codehsize=91\wd0 % 88 columns wasn't enough; I don't know why +\newdimen\codemargin +\codemargin=0pt +\newdimen\nwdefspace +\nwdefspace=\codehsize +% need to use \textwidth in {\LaTeX} to handle styles with +% non-standard margins (David Bruce). Don't know why we sometimes +% wanted \hsize. 27 August 1997. +%% \advance\nwdefspace by -\hsize\relax +\ifx\textwidth\undefined + \advance\nwdefspace by -\hsize\relax +\else + \advance\nwdefspace by -\textwidth\relax +\fi +\chardef\other=12 +\def\setupcode{% + \chardef\\=`\\ + \chardef\{=`\{ + \chardef\}=`\} + \catcode`\$=\other + \catcode`\&=\other + \catcode`\#=\other + \catcode`\%=\other + \catcode`\~=\other + \catcode`\_=\other + \catcode`\^=\other + \catcode`\"=\other % fixes problem with german.sty + \obeyspaces\Tt +} +%\let\nwlbrace=\{ +%\let\nwrbrace=\} +\def\nwendquote{\relax\ifhmode\spacefactor=1000 \fi} +{\catcode`\^^M=\active % make CR an active character + \gdef\newlines{\catcode`\^^M=\active % make CR an active character + \def^^M{\par\startline}}% + \gdef\eatline#1^^M{\relax}% +} +%%% DON'T \gdef^^M{\par\startline}}% in case ^^M appears in a \write +\def\startline{\noindent\hskip\parindent\ignorespaces} +\def\nwnewline{\ifvmode\else\hfil\break\leavevmode\hbox{}\fi} +\def\setupmodname{% + \catcode`\$=3 + \catcode`\&=4 + \catcode`\#=6 + \catcode`\%=14 + \catcode`\~=13 + \catcode`\_=8 + \catcode`\^=7 + \catcode`\ =10 + \catcode`\^^M=5 + \let\nwlbrace\lbrace + \let\nwrbrace\rbrace + \let\{\nwlbrace + \let\}\nwrbrace + % bad news --- don't know what catcode to give " + \Rm} +\def\LA{\begingroup\maybehbox\bgroup\setupmodname\It$\langle$} +\def\RA{\/$\rangle$\egroup\endgroup} +\def\code{\leavevmode\begingroup\setupcode\newlines} +\def\edoc{\endgroup} +\let\maybehbox\relax +\newbox\equivbox +\setbox\equivbox=\hbox{$\equiv$} +\newbox\plusequivbox +\setbox\plusequivbox=\hbox{$\mathord{+}\mathord{\equiv}$} +% \moddef can't have an argument because there might be \code...\edoc +\def\moddef{\leavevmode\kern-\codemargin\LA} +\def\endmoddef{\RA\ifmmode\equiv\else\unhcopy\equivbox\fi + \nobreak\hfill\nobreak} +\def\plusendmoddef{\RA\ifmmode\mathord{+}\mathord{\equiv}\else\unhcopy\plusequivbox\fi + \nobreak\hfill\nobreak} +\def\chunklist{% +\errhelp{I changed \chunklist to \nowebchunks. +I'll try to avoid such incompatible changes in the future.}% +\errmessage{Use \string\nowebchunks\space instead of \string\chunklist}} +\def\nowebchunks{\message{}} +\def\nowebindex{\message{}} +% here is support for the new-style (capitalized) font-changing commands +% thanks to Dave Love +\ifx\documentstyle\undefined + \let\Rm=\rm \let\It=\it \let\Tt=\tt % plain +\else\ifx\selectfont\undefined + \let\Rm=\rm \let\It=\it \let\Tt=\tt % LaTeX OFSS +\else % LaTeX NFSS + \def\Rm{\reset@font\rm} + \def\It{\reset@font\it} + \def\Tt{\reset@font\tt} + \def\Bf{\reset@font\bf} +\fi\fi +\ifx\reset@font\undefined \let\reset@font=\relax \fi +\def\noweboptions#1{% + \def\@nwoptionlist{#1}% + \@for\@nwoption:=\@nwoptionlist\do{% + \@ifundefined{nwopt@\@nwoption}{% + \@latexerr{There is no such noweb option as '\@nwoption'}\@eha}{% + \csname nwopt@\@nwoption\endcsname}}} +\codemargin=10pt +\advance\codehsize by \codemargin % make room for indentation of code +\advance\nwdefspace by \codemargin % and fix adjustment for def/use +\def\setcodemargin#1{% + \advance\codehsize by -\codemargin % make room for indentation of code + \advance\nwdefspace by -\codemargin % and fix adjustment for def/use + \codemargin=#1 + \advance\codehsize by \codemargin % make room for indentation of code + \advance\nwdefspace by \codemargin % and fix adjustment for + % def/use +} +\def\nwopt@shift{% + \dimen@=-0.8in + \if@twoside % Values for two-sided printing: + \advance\evensidemargin by \dimen@ + \else % Values for one-sided printing: + \advance\evensidemargin by \dimen@ + \advance\oddsidemargin by \dimen@ + \fi +% \advance \marginparwidth -\dimen@ +} +\let\nwopt@noshift\@empty +\def\nwbegincode#1{% + \begingroup + \topsep \nwcodetopsep + \@beginparpenalty \@highpenalty + \@endparpenalty -\@highpenalty + \@begincode } +\def\nwendcode{\endtrivlist \endgroup \filbreak} % keeps code on 1 page + +\newenvironment{webcode}{% + \@begincode +}{% + \endtrivlist} +\def\@begincode{% + \trivlist \item[]% + \leftskip\@totalleftmargin \advance\leftskip\codemargin + \rightskip\hsize \advance\rightskip -\codehsize + \parskip\z@ \parindent\z@ \parfillskip\@flushglue + \linewidth\codehsize + \@@par + \def\par{\leavevmode\null \@@par \penalty\nwcodepenalty}% + \obeylines + \@noligs \ifx\verbatim@nolig@list\undefined\else + \let\do=\nw@makeother \verbatim@nolig@list \do@noligs\` + \fi + \setupcode \frenchspacing \@vobeyspaces + \nowebsize \setupcode + \let\maybehbox\mbox } + \newskip\nwcodetopsep \nwcodetopsep = 3pt plus 1.2pt minus 1pt + \let\nowebsize=\normalsize + \def\nwopt@tinycode{\let\nowebsize=\tiny} + \def\nwopt@footnotesizecode{\let\nowebsize=\footnotesize} + \def\nwopt@scriptsizecode{\let\nowebsize=\scriptsize} + \def\nwopt@smallcode{\let\nowebsize=\small} + \def\nwopt@normalsizecode{\let\nowebsize=\normalsize} + \def\nwopt@largecode{\let\nowebsize=\large} + \def\nwopt@Largecode{\let\nowebsize=\Large} + \def\nwopt@LARGEcode{\let\nowebsize=\LARGE} + \def\nwopt@hugecode{\let\nowebsize=\huge} + \def\nwopt@Hugecode{\let\nowebsize=\Huge} +\newcount\nwcodepenalty \nwcodepenalty=\@highpenalty +\def\nw@makeother#1{\catcode`#1=12 } +\def\nwbegindocs#1{\ifvmode\noindent\fi} +\let\nwenddocs=\relax +\let\nwdocspar=\filbreak +\def\@nwsemifilbreak#1{\vskip0pt plus#1\penalty-200\vskip0pt plus -#1} +\newdimen\nwbreakcodespace +\nwbreakcodespace=0.2in % by default, leave no more than this on a page +\def\nwopt@breakcode{% + \def\nwdocspar{\@nwsemifilbreak{0.2in}}% + \def\nwendcode{\endtrivlist\endgroup} % ditches filbreak +} +\raggedbottom +\def\code{\leavevmode\begingroup\setupcode\@vobeyspaces\obeylines} +\let\edoc=\endgroup +\newdimen\@original@textwidth +\def\ps@noweb{% + \@original@textwidth=\textwidth + \let\@mkboth\@gobbletwo + \def\@oddfoot{}\def\@evenfoot{}% No feet. + \if@twoside % If two-sided printing. + \def\@evenhead{\hbox to \@original@textwidth{% + \Rm \thepage\qquad{\Tt\leftmark}\hfil\today}}% Left heading. + \def\@oddhead{\hbox to \@original@textwidth{% + \Rm \today\hfil{\Tt\leftmark}\qquad\thepage}}% Right heading. + \else % If one-sided printing. + \def\@oddhead{\hbox to \@original@textwidth{% + \Rm \today\hfil{\Tt\leftmark}\qquad\thepage}}% Right heading. + \let\@evenhead\@oddhead + \fi + \let\chaptermark\@gobble + \let\sectionmark\@gobble + \let\subsectionmark\@gobble + \let\subsubsectionmark\@gobble + \let\paragraphmark\@gobble + \let\subparagraphmark\@gobble + \def\nwfilename{\begingroup\let\do\@makeother\dospecials + \catcode`\{=1 \catcode`\}=2 \nw@filename} + \def\nw@filename##1{\endgroup\markboth{##1}{##1}\let\nw@filename=\nw@laterfilename}% +} +\def\nw@laterfilename#1{\endgroup\clearpage \markboth{#1}{#1}} +\let\nwfilename=\@gobble +\def\nwcodecomment#1{\@@par\penalty\nwcodepenalty + \if@firstnwcodecomment + \vskip\nwcodecommentsep\penalty\nwcodepenalty\@firstnwcodecommentfalse + \fi% + \hspace{-\codemargin}{% + \rightskip=0pt plus1in + \interlinepenalty\nwcodepenalty + \let\\\relax\footnotesize\Rm #1\@@par\penalty\nwcodepenalty}} +\def\@nwalsodefined#1{\nwcodecomment{\@nwlangdepdef\ \nwpageprep\ \@pagesl{#1}.}} +\def\@nwused#1{\nwcodecomment{\@nwlangdepcud\ \nwpageprep\ \@pagesl{#1}.}} +\def\@nwnotused#1{\nwcodecomment{\@nwlangdeprtc.}} +\def\nwoutput#1{\nwcodecomment{\@nwlangdepcwf\ {\Tt \@stripstar#1*\stripped}.}} +\def\@stripstar#1*#2\stripped{#1} +\newcommand{\nwprevdefptr}[1]{% + \mbox{$\mathord{\triangleleft}\,\mathord{\mbox{\subpageref{#1}}}$}} +\newcommand{\nwnextdefptr}[1]{% + \mbox{$\mathord{\mbox{\subpageref{#1}}}\,\mathord{\triangleright}$}} + +\newcommand{\@nwprevnextdefs}[2]{% + {\nwtagstyle + \ifx\relax#1\else ~~\nwprevdefptr{#1}\fi + \ifx\relax#2\else ~~\nwnextdefptr{#2}\fi}} +\newcommand{\@nwusesondefline}[1]{{\nwtagstyle~~(\@pagenumsl{#1})}} +\newcommand{\@nwstartdeflinemarkup}{\nobreak\hskip 1.5em plus 1fill\nobreak} +\newcommand{\@nwenddeflinemarkup}{\nobreak\hskip \nwdefspace minus\nwdefspace\nobreak} +\def\nwopt@longxref{% + \let\nwalsodefined\@nwalsodefined + \let\nwused\@nwused + \let\nwnotused\@nwnotused + \let\nwprevnextdefs\@gobbletwo + \let\nwusesondefline\@gobble + \let\nwstartdeflinemarkup\relax + \let\nwenddeflinemarkup\relax +} +\def\nwopt@shortxref{% + \let\nwalsodefined\@gobble + \let\nwused\@gobble + \let\nwnotused\@gobble + \let\nwprevnextdefs\@nwprevnextdefs + \let\nwusesondefline\@nwusesondefline + \let\nwstartdeflinemarkup\@nwstartdeflinemarkup + \let\nwenddeflinemarkup\@nwenddeflinemarkup +} +\def\nwopt@noxref{% + \let\nwalsodefined\@gobble + \let\nwused\@gobble + \let\nwnotused\@gobble + \let\nwprevnextdefs\@gobbletwo + \let\nwusesondefline\@gobble + \let\nwstartdeflinemarkup\relax + \let\nwenddeflinemarkup\relax +} +\nwopt@shortxref % to hell with backward compatibility! +\newskip\nwcodecommentsep \nwcodecommentsep=3pt plus 1pt minus 1pt +\newif\if@firstnwcodecomment\@firstnwcodecommenttrue +\newcount\@nwlopage\newcount\@nwhipage % range lo..hi-1 +\newcount\@nwlosub % subpage of lo +\newcount\@nwhisub % subpage of hi +\def\@nwfirstpage#1#2#3{% subpage page xref-tag + \@nwlopage=#2 \@nwlosub=#1 + \def\@nwloxreftag{#3}% + \advance\@nwpagecount by \@ne + \@nwhipage=\@nwlopage\advance\@nwhipage by \@ne } +\def\@nwnextpage#1#2#3{% subpage page xref-tag + \ifnum\@nwhipage=#2 + \advance\@nwhipage by \@ne + \advance\@nwpagecount by \@ne + \@nwhisub=#1 + \def\@nwhixreftag{#3}\else + \ifnum#2<\@nwlopage \advance\@nwhipage by \m@ne + \ifnum\@nwhipage=\@nwlopage + \edef\@tempa{\noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}% + {\@nwloxreftag}}}% + \else + \count@=\@nwhipage \advance\count@ by \m@ne + \ifnum\count@=\@nwlopage % consecutive pages + \edef\@tempa{\noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}% + {\@nwloxreftag}}% + \noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}} + {\@nwhixreftag}}}% + \else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else + \count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100 + \ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else + \count@=\@nwlopage \divide\count@ by 100 + \@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100 + \ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi + \multiply\@nwpagetemp by 100 + \advance \@nwhipage by -\@nwpagetemp + \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}% + \else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}% + \fi + \fi + \fi% + \fi + \fi% + \edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa\@nwfirstpage{#1}{#2}{#3}\else + \ifnum#2>\@nwhipage \advance\@nwhipage by \m@ne + \ifnum\@nwhipage=\@nwlopage + \edef\@tempa{\noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}% + {\@nwloxreftag}}}% + \else + \count@=\@nwhipage \advance\count@ by \m@ne + \ifnum\count@=\@nwlopage % consecutive pages + \edef\@tempa{\noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}% + {\@nwloxreftag}}% + \noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}} + {\@nwhixreftag}}}% + \else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else + \count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100 + \ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else + \count@=\@nwlopage \divide\count@ by 100 + \@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100 + \ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi + \multiply\@nwpagetemp by 100 + \advance \@nwhipage by -\@nwpagetemp + \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}% + \else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}% + \fi + \fi + \fi% + \fi + \fi% + \edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa\@nwfirstpage{#1}{#2}{#3}\else + \@nwlosub=0 \@nwhisub=0 + \fi\fi\fi + } +\newcount\@nwpagetemp +\newcount\@nwpagecount +\def\@nwfirstpagel#1{% label + \@ifundefined{r@#1}{\@warning{Reference `#1' on page \thepage \space undefined}% + \nwix@cons\nw@pages{\\{\bf ??}}}{% + \edef\@tempa{\noexpand\@nwfirstpage\subpagepair{#1}{#1}}\@tempa}} +\def\@nwnextpagel#1{% label + \@ifundefined{r@#1}{\@warning{Reference `#1' on page \thepage \space undefined}% + \nwix@cons\nw@pages{\\{\bf ??}}}{% + \edef\@tempa{\noexpand\@nwnextpage\subpagepair{#1}{#1}}\@tempa}} +\def\@pagesl#1{% list of labels + \gdef\nw@pages{}\@nwpagecount=0 + \def\\##1{\@nwfirstpagel{##1}\let\\=\@nwnextpagel}#1% + \advance\@nwhipage by \m@ne + \ifnum\@nwhipage=\@nwlopage + \edef\@tempa{\noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}% + {\@nwloxreftag}}}% + \else + \count@=\@nwhipage \advance\count@ by \m@ne + \ifnum\count@=\@nwlopage % consecutive pages + \edef\@tempa{\noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}% + {\@nwloxreftag}}% + \noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}} + {\@nwhixreftag}}}% + \else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else + \count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100 + \ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else + \count@=\@nwlopage \divide\count@ by 100 + \@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100 + \ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi + \multiply\@nwpagetemp by 100 + \advance \@nwhipage by -\@nwpagetemp + \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}% + \else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}% + \fi + \fi + \fi% + \fi + \fi% + \edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa\def\\##1{\@nwhyperpagenum##1}% + \ifnum\@nwpagecount=1 \nwpageword \else \nwpagesword\fi~\commafy{\nw@pages}} +\def\@nwhyperpagenum#1#2{\nwhyperreference{#2}{#1}} + +\def\@pagenumsl#1{% list of labels -- doesn't include word `pages', commas, or `and' + \gdef\nw@pages{}\@nwpagecount=0 + \def\\##1{\@nwfirstpagel{##1}\let\\=\@nwnextpagel}#1% + \advance\@nwhipage by \m@ne + \ifnum\@nwhipage=\@nwlopage + \edef\@tempa{\noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}% + {\@nwloxreftag}}}% + \else + \count@=\@nwhipage \advance\count@ by \m@ne + \ifnum\count@=\@nwlopage % consecutive pages + \edef\@tempa{\noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}% + {\@nwloxreftag}}% + \noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}} + {\@nwhixreftag}}}% + \else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else + \count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100 + \ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else + \count@=\@nwlopage \divide\count@ by 100 + \@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100 + \ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi + \multiply\@nwpagetemp by 100 + \advance \@nwhipage by -\@nwpagetemp + \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}% + \else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}% + \fi + \fi + \fi% + \fi + \fi% + \edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa% + \def\\##1{\@nwhyperpagenum##1\let\\=\@nwpagenumslrest}\nw@pages} +\def\@nwpagenumslrest#1{~\@nwhyperpagenum#1} +\def\subpages#1{% list of {{subpage}{page}} + \gdef\nw@pages{}\@nwpagecount=0 + \def\\##1{\edef\@tempa{\noexpand\@nwfirstpage##1{}}\@tempa + \def\\####1{\edef\@tempa{\noexpand\@nwnextpage####1}\@tempa}}#1% + \advance\@nwhipage by \m@ne + \ifnum\@nwhipage=\@nwlopage + \edef\@tempa{\noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}% + {\@nwloxreftag}}}% + \else + \count@=\@nwhipage \advance\count@ by \m@ne + \ifnum\count@=\@nwlopage % consecutive pages + \edef\@tempa{\noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}% + {\@nwloxreftag}}% + \noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}} + {\@nwhixreftag}}}% + \else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else + \count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100 + \ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else + \count@=\@nwlopage \divide\count@ by 100 + \@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100 + \ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi + \multiply\@nwpagetemp by 100 + \advance \@nwhipage by -\@nwpagetemp + \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}% + \else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}% + \fi + \fi + \fi% + \fi + \fi% + \edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa\def\\##1{\@firstoftwo##1}% + \ifnum\@nwpagecount=1 \nwpageword \else \nwpagesword\fi~\commafy{\nw@pages}} +\def\@nwaddrange{\advance\@nwhipage by \m@ne + \ifnum\@nwhipage=\@nwlopage + \edef\@tempa{\noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}% + {\@nwloxreftag}}}% + \else + \count@=\@nwhipage \advance\count@ by \m@ne + \ifnum\count@=\@nwlopage % consecutive pages + \edef\@tempa{\noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwlosub}{\number\@nwlopage}}% + {\@nwloxreftag}}% + \noexpand\noexpand\noexpand\\% + {{\nwthepagenum{\number\@nwhisub}{\number\@nwhipage}} + {\@nwhixreftag}}}% + \else \ifnum\@nwlopage<110 \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else + \count@=\@nwlopage \divide\count@ by 100 \multiply\count@ by 100 + \ifnum\count@=\@nwlopage \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}\else + \count@=\@nwlopage \divide\count@ by 100 + \@nwpagetemp=\@nwhipage \divide\@nwpagetemp by 100 + \ifnum\count@=\@nwpagetemp % lo--least 2 digits of hi + \multiply\@nwpagetemp by 100 + \advance \@nwhipage by -\@nwpagetemp + \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}% + \else \edef\@tempa{\noexpand\noexpand\noexpand\\{{\number\@nwlopage--\number\@nwhipage}{}}}% + \fi + \fi + \fi% + \fi + \fi% + \edef\@tempa{\noexpand\nwix@cons\noexpand\nw@pages{\@tempa}}\@tempa} +\def\nwpageword{\@nwlangdepchk} % chunk, was page +\def\nwpagesword{\@nwlangdepchks} % chunk, was page +\def\nwpageprep{\@nwlangdepin} % in, was on +\newcommand\nw@genericref[2]{% what to do, name of ref + \expandafter\nw@g@nericref\csname r@#2\endcsname#1{#2}} +\newcommand\nw@g@nericref[3]{% control sequence, what to do, name + \ifx#1\relax + \ref{#3}% trigger the standard `undefined ref' mechanisms + \else + \expandafter#2#1.\\% + \fi} +\def\nw@selectone#1#2#3\\{#1} +\def\nw@selecttwo#1#2#3\\{#2} +\def\nw@selectonetwo#1#2#3\\{{#1}{#2}} +\newcommand{\subpageref}[1]{% + \nwhyperreference{#1}{\nw@genericref\@subpageref{#1}}} +\def\@subpageref#1#2#3\\{% + \@ifundefined{2on#2}{#2}{\nwthepagenum{#1}{#2}}} +\newcommand{\subpagepair}[1]{% % produces {subpage}{page} + \@ifundefined{r@#1}% + {{0}{0}}% + {\nw@genericref\@subpagepair{#1}}} +\def\@subpagepair#1#2#3\\{% + \@ifundefined{2on#2}{{0}{#2}}{{#1}{#2}}} +\newcommand{\sublabel}[1]{% + \leavevmode % needed to make \@bsphack work + \@bsphack + \nwblindhyperanchor{#1}% + \if@filesw {\let\thepage\relax + \def\protect{\noexpand\noexpand\noexpand}% + \edef\@tempa{\write\@auxout{\string + \newsublabel{#1}{{}{\thepage}}}}% + \expandafter}\@tempa + \if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack} +\newcommand{\nosublabel}[1]{% + \@bsphack\if@filesw {\let\thepage\relax + \def\protect{\noexpand\noexpand\noexpand}% + \edef\@tempa{\write\@auxout{\string + \newlabel{#1}{{0}{\thepage}}}}% + \expandafter}\@tempa + \if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack} +\newcommand\newsublabel{% + \nw@settrailers + \global\let\newsublabel\@newsublabel + \@newsublabel} +\newcommand{\@newsublabel}[2]{% + \edef\this@page{\@cdr#2\@nil}% + \ifx\this@page\last@page\else + \sub@page=\z@ + \fi + \edef\last@page{\this@page} + \advance\sub@page by \@ne + \ifnum\sub@page=\tw@ + \global\@namedef{2on\this@page}{}% + \fi + \pendingsublabel{#1}% + \edef\@tempa##1{\noexpand\newlabel{##1}% + {{\number\sub@page}{\this@page}\nw@labeltrailers}}% + \pending@sublabels + \def\pending@sublabels{}} +\newcommand\nw@settrailers{% -- won't work on first run + \@ifpackageloaded{nameref}% + {\gdef\nw@labeltrailers{{}{}{}}}% + {\gdef\nw@labeltrailers{}}} +\renewcommand\nw@settrailers{% + \@ifundefined{@secondoffive}% + {\gdef\nw@labeltrailers{}}% + {\gdef\nw@labeltrailers{{}{}{}}}} +\newcommand{\nextchunklabel}[1]{% + \nwblindhyperanchor{#1}% % looks slightly bogus --- nr + \@bsphack\if@filesw {\let\thepage\relax + \edef\@tempa{\write\@auxout{\string\pendingsublabel{#1}}}% + \expandafter}\@tempa + \if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack} +\newcommand\pendingsublabel[1]{% + \def\@tempa{\noexpand\@tempa}% + \edef\pending@sublabels{\noexpand\@tempa{#1}\pending@sublabels}} +\def\pending@sublabels{} +\def\last@page{\relax} +\newcount\sub@page +\def\@alphasubpagenum#1#2{#2\ifnum#1=0 \else\@alph{#1}\fi} +\def\@nosubpagenum#1#2{#2} +\def\@numsubpagenum#1#2{#2\ifnum#1=0 \else.\@arabic{#1}\fi} +\def\nwopt@nosubpage{\let\nwthepagenum=\@nosubpagenum\nwopt@nomargintag} +\def\nwopt@numsubpage{\let\nwthepagenum=\@numsubpagenum} +\def\nwopt@alphasubpage{\let\nwthepagenum=\@alphasubpagenum} +\nwopt@alphasubpage +\newcount\@nwalph@n +\let\@nwalph@d\@tempcnta +\let\@nwalph@bound\@tempcntb +\def\@nwlongalph#1{{% + \@nwalph@n=#1\advance\@nwalph@n by-1 + \@nwalph@bound=26 + \loop\ifnum\@nwalph@n<\@nwalph@bound\else + \advance\@nwalph@n by -\@nwalph@bound + \multiply\@nwalph@bound by 26 + \repeat + \loop\ifnum\@nwalph@bound>1 + \divide\@nwalph@bound by 26 + \@nwalph@d=\@nwalph@n\divide\@nwalph@d by \@nwalph@bound + % d := d * bound ; n -:= d; d := d / bound --- saves a temporary + \multiply\@nwalph@d by \@nwalph@bound + \advance\@nwalph@n by -\@nwalph@d + \divide\@nwalph@d by \@nwalph@bound + \advance\@nwalph@d by 1 \@alph{\@nwalph@d}% + \repeat +}} +\newcount\nw@chunkcount +\nw@chunkcount=\@ne +\newcommand{\weblabel}[1]{% + \@bsphack + \nwblindhyperanchor{#1}% + \if@filesw {\let\thepage\relax + \def\protect{\noexpand\noexpand\noexpand}% + \edef\@tempa{\write\@auxout{\string + \newsublabel{#1}{{}{\number\nw@chunkcount}}}}% + \expandafter}\@tempa + \global\advance\nw@chunkcount by \@ne + \if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack} +\def\nwopt@webnumbering{% + \let\sublabel=\weblabel + \def\nwpageword{chunk}\def\nwpagesword{chunks}% + \def\nwpageprep{in}} +% \nwindexdefn{printable name}{identifying label}{label of chunk} +% \nwindexuse{printable name}{identifying label}{label of chunk} + +\def\nwindexdefn#1#2#3{\@auxix{\protect\nwixd}{#2}{#3}} +\def\nwindexuse#1#2#3{\@auxix{\protect\nwixu}{#2}{#3}} + +\def\@auxix#1#2#3{% {marker}{id label}{subpage label} + \@bsphack\if@filesw {\let\nwixd\relax\let\nwixu\relax + \def\protect{\noexpand\noexpand\noexpand}% + \edef\@tempa{\write\@auxout{\string\nwixadd{#1}{#2}{#3}}}% + \expandafter}\@tempa + \if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack} +% \nwixadd{marker}{idlabel}{subpage label} +\def\nwixadd#1#2#3{% + \@ifundefined{nwixl@#2}% + {\global\@namedef{nwixl@#2}{#1{#3}}}% + {\expandafter\nwix@cons\csname nwixl@#2\endcsname{#1{#3}}}} +\def\@nwsubscriptident#1#2{\mbox{$\mbox{#1}_{\mathrm{\subpageref{#2}}}$}} +\def\@nwnosubscriptident#1#2{#1} +\def\@nwhyperident#1#2{\leavevmode\nwhyperreference{#2}{#1}} +\def\nwopt@subscriptidents{% + \let\nwlinkedidentq\@nwsubscriptident + \let\nwlinkedidentc\@nwsubscriptident +} +\def\nwopt@nosubscriptidents{% + \let\nwlinkedidentq\@nwnosubscriptident + \let\nwlinkedidentc\@nwnosubscriptident +} +\def\nwopt@hyperidents{% + \let\nwlinkedidentq\@nwhyperident + \let\nwlinkedidentc\@nwhyperident +} +\def\nwopt@nohyperidents{% + \let\nwlinkedidentq\@nwnosubscriptident + \let\nwlinkedidentc\@nwnosubscriptident +} +\def\nwopt@subscriptquotedidents{% + \let\nwlinkedidentq\@nwsubscriptident +} +\def\nwopt@nosubscriptquotedidents{% + \let\nwlinkedidentq\@nwnosubscriptident +} +\def\nwopt@hyperquotedidents{% + \let\nwlinkedidentq\@nwhyperident +} +\def\nwopt@nohyperquotedidents{% + \let\nwlinkedidentq\@nwnosubscriptident +} +\nwopt@hyperidents +\newcount\@commacount +\def\commafy#1{% + {\nwix@listcount{#1}\@commacount=\nwix@counter + \let\@comma@each=\\% + \ifcase\@commacount\let\\=\@comma@each\or\let\\=\@comma@each\or + \def\\{\def\\{ \@nwlangdepand\ \@comma@each}\@comma@each}\else + \def\\{\def\\{, % + \advance\@commacount by \m@ne + \ifnum\@commacount=1 \@nwlangdepand~\fi\@comma@each}\@comma@each}\fi + #1}} +\def\nwix@cons#1#2{% {list}{\marker{element}} + {\toks0=\expandafter{#1}\def\@tempa{#2}\toks2=\expandafter{\@tempa}% + \xdef#1{\the\toks0 \the\toks2 }}} +\def\nwix@uses#1{% {label} + \def\nwixu{\\}\let\nwixd\@gobble\@nameuse{nwixl@#1}} +\def\nwix@defs#1{% {label} + \def\nwixd{\\}\let\nwixu\@gobble\@nameuse{nwixl@#1}} +\newcount\nwix@counter +\def\nwix@listcount#1{% {list with \\} + {\count@=0 + \def\\##1{\advance\count@ by \@ne }% + #1\global\nwix@counter=\count@ }} +\def\nwix@usecount#1{\nwix@listcount{\nwix@uses{#1}}} +\def\nwix@defcount#1{\nwix@listcount{\nwix@defs{#1}}} +\def\nwix@id@defs#1{% index pair + {{\Tt \@car#1\@nil}% + \def\\##1{\nwix@defs@space\subpageref{##1}}\nwix@defs{\@cdr#1\@nil}}} + % useful above to change ~ into something that can break +% this option is undocumented because I think breakdefs is always right +\def\nwopt@breakdefs{\def\nwix@defs@space{\penalty200\ }} +\def\nwopt@nobreakdefs{\def\nwix@defs@space{~}} % old code +\nwopt@breakdefs +\def\nwidentuses#1{% list of index pairs + \nwcodecomment{\@nwlangdepuss\ \let\\=\nwix@id@defs\commafy{#1}.}} +\def\nwix@totaluses#1{% list of index pairs + {\count@=0 + \def\\##1{\nwix@usecount{\@cdr##1\@nil}\advance\count@ by\nwix@counter}% + #1\global\nwix@counter\count@ }} +\def\nwix@id@uses#1#2{% {ident}{label} + \nwix@usecount{#2}\ifnum\nwix@counter>0 + {\advance\leftskip by \codemargin + \nwcodecomment{{\Tt #1}, \@nwlangdepusd\ \nwpageprep\ \@pagesl{\nwix@uses{#2}}.}}% + \else + \ifnw@hideunuseddefs\else + {\advance\leftskip by \codemargin \nwcodecomment{{\Tt #1}, \@nwlangdepnvu.}}% + \fi + \fi} +\def\nwidentdefs#1{% list of index pairs + \ifnw@hideunuseddefs\nwix@totaluses{#1}\else\nwix@listcount{#1}\fi + \ifnum\nwix@counter>0 + \nwcodecomment{\@nwlangdepdfs:}% + {\def\\##1{\nwix@id@uses ##1}#1}% + \fi} +\newif\ifnw@hideunuseddefs\nw@hideunuseddefsfalse +\def\nwopt@hideunuseddefs{\nw@hideunuseddefstrue} +\def\nwopt@noidentxref{% + \let\nwidentdefs\@gobble + \let\nwidentuses\@gobble} +\def\nw@underlinedefs{% {list with \nwixd, \nwixu} + \let\\=\relax\def\nw@comma{, } + \def\nwixd##1{\\\underline{\subpageref{##1}}\let\\\nw@comma}% + \def\nwixu##1{\\\subpageref{##1}\let\\\nw@comma}} + +\def\nw@indexline#1#2{% + {\indent {\Tt #1}: \nw@underlinedefs\@nameuse{nwixl@#2}\par}} + +\newenvironment{thenowebindex}{\parindent=-10pt \parskip=\z@ + \advance\leftskip by 10pt + \advance\rightskip by 0pt plus1in\par\@afterindenttrue + \def\\##1{\nw@indexline##1}}{} +\def\nowebindex{% + \@ifundefined{nwixs@i}% + {\@warning{The \string\nowebindex\space is empty}}% + {\begin{thenowebindex}\@nameuse{nwixs@i}\end{thenowebindex}}} +\def\nowebindex@external{% + {\let\nwixadds@c=\@gobble + \def\nwixadds@i##1{\nw@indexline##1}% + \def\nwixaddsx##1##2{\@nameuse{nwixadds@##1}{##2}}% + \begin{thenowebindex}\@input{\jobname.nwi}\end{thenowebindex}}} +\def\nwixlogsorted#1#2{% list data + \@bsphack\if@filesw + \toks0={#2}\immediate\write\@auxout{\string\nwixadds{#1}{\the\toks0}} + \if@nobreak \ifvmode\nobreak\fi\fi\fi\@esphack} +\def\nwixadds#1#2{% + \@ifundefined{nwixs@#1}% + {\global\@namedef{nwixs@#1}{\\{#2}}}% + {\expandafter\nwix@cons\csname nwixs@#1\endcsname{\\{#2}}}} +\let\nwixaddsx=\@gobbletwo +\def\nwopt@externalindex{% + \ifx\nwixadds\@gobbletwo % already called + \else + \let\nwixaddsx=\nwixadds \let\nwixadds=\@gobbletwo + \let\nowebindex=\nowebindex@external + \let\nowebchunks=\nowebchunks@external + \fi} +\def\nowebchunks{% + \@ifundefined{nwixs@c}% + {\@warning{The are no \string\nowebchunks}}% + {\begin{thenowebchunks}\@nameuse{nwixs@c}\end{thenowebchunks}}} +\def\nowebchunks@external{% + {\let\nwixadds@i=\@gobble + \def\nwixadds@c##1{\nw@onechunk##1}% + \def\nwixaddsx##1##2{\@nameuse{nwixadds@##1}{##2}}% + \begin{thenowebchunks}\@input{\jobname.nwi}\end{thenowebchunks}}} + \@namedef{r@nw@notdef}{{0}{(\@nwlangdepnvd)}} +\def\nw@chunkunderlinedefs{% {list of labels with \nwixd, \nwixu} + \let\\=\relax\def\nw@comma{, } + \def\nwixd##1{\\\underline{\subpageref{##1}}\let\\\nw@comma}% + \def\nwixu##1{\\\subpageref{##1}\let\\\nw@comma}} +\def\nw@onechunk#1#2#3{% {name}{label of first definition}{list with \nwixd, \nwixu} + \@ifundefined{r@#2}{}{% + \indent\LA #1~{\nwtagstyle\subpageref{#2}}\RA + \if@nwlongchunks{~\nw@chunkunderlinedefs#3}\fi\par}} +\newenvironment{thenowebchunks}{\vskip3pt + \parskip=\z@\parindent=-10pt \advance\leftskip by 10pt + \advance\rightskip by 0pt plus10pt \@afterindenttrue + \def\\##1{\nw@onechunk##1}}{} +\newif\if@nwlongchunks +\@nwlongchunksfalse +\let\nwopt@longchunks\@nwlongchunkstrue +\newcommand\@nw@hyper@ref{\hyperreference} % naras +\newcommand\@nw@hyper@anc{\blindhyperanchor} % naras +\newcommand\@nw@hyperref@ref[2]{\hyperlink{noweb.#1}{#2}} % nr +\newcommand\@nw@hyperref@anc[1]{\hypertarget{noweb.#1}{\relax}} % nr +%%\renewcommand\@nw@hyperref@ref[2]{{#2}} % nr +%%\renewcommand\@nw@hyperref@anc[1]{} % nr +\newcommand\nwhyperreference{% + \@ifundefined{hyperlink} + {\@ifundefined{hyperreference} + {\global\let\nwhyperreference\@gobble} + {\global\let\nwhyperreference\@nw@hyper@ref}} + {\global\let\nwhyperreference\@nw@hyperref@ref}% + \nwhyperreference +} + +\newcommand\nwblindhyperanchor{% + \@ifundefined{hyperlink} + {\@ifundefined{hyperreference} + {\global\let\nwblindhyperanchor\@gobble} + {\global\let\nwblindhyperanchor\@nw@hyper@anc}} + {\global\let\nwblindhyperanchor\@nw@hyperref@anc}% + \nwblindhyperanchor +} +\newcommand\nwanchorto{% + \begingroup\let\do\@makeother\dospecials + \catcode`\{=1 \catcode`\}=2 \nw@anchorto} +\newcommand\nw@anchorto[1]{\endgroup\def\nw@next{#1}\nw@anchortofin} +\newcommand\nw@anchortofin[1]{#1\footnote{See URL \texttt{\nw@next}.}} +\let\nwanchorname\@gobble +\newif\ifhtml +\htmlfalse +\let\nwixident=\relax +\def\nwbackslash{\char92} +\def\nwlbrace{\char123} +\def\nwrbrace{\char125} +\def\nwopt@english{% + \def\@nwlangdepdef{This definition is continued}% + \def\@nwlangdepcud{This code is used}% + \def\@nwlangdeprtc{Root chunk (not used in this document)}% + \def\@nwlangdepcwf{This code is written to file}% + \def\@nwlangdepchk{chunk}% + \def\@nwlangdepchks{chunks}% + \def\@nwlangdepin{in}% + \def\@nwlangdepand{and}% + \def\@nwlangdepuss{Uses}% + \def\@nwlangdepusd{used}% + \def\@nwlangdepnvu{never used}% + \def\@nwlangdepdfs{Defines}% + \def\@nwlangdepnvd{never defined}% +} +\let\nwopt@american\nwopt@english +\def\nwopt@portuges{% + \def\@nwlangdepdef{Defini\c{c}\~ao continuada em}% + % This definition is continued + \def\@nwlangdepcud{C\'odigo usado em}% + % This code is used + \def\@nwlangdeprtc{Fragmento de topo (sem uso no documento)}% + % Root chunk (not used in this document) + \def\@nwlangdepcwf{Este c\'odigo foi escrito no ficheiro}% + % This code is written to file + \def\@nwlangdepchk{fragmento}% + % chunk + \def\@nwlangdepchks{fragmentos}% + % chunks + \def\@nwlangdepin{no(s)}% + % in + \def\@nwlangdepand{e}% + % and + \def\@nwlangdepuss{Usa}% + % Uses + \def\@nwlangdepusd{usado}% + % used + \def\@nwlangdepnvu{nunca usado}% + % never used + \def\@nwlangdepdfs{Define}% + % Defines + \def\@nwlangdepnvd{nunca definido}% + % never defined +} +\def\nwopt@brazil{% + \def\@nwlangdepdef{Defini\c{c}\~{a}o continua}% + % This definition is continued + \def\@nwlangdepcud{C\'odigo usado}% + % This code is used + \def\@nwlangdeprtc{Trecho raiz, isto é, este trecho come\c{c}a aqui}% + % Root chunk (not used in this document) + \def\@nwlangdepcwf{C\'odigo escrito no arquivo}% + % This code is written to file + \def\@nwlangdepchk{trecho}% + % chunk + \def\@nwlangdepchks{trechos}% + % chunks + \def\@nwlangdepin{@}% + % in + \def\@nwlangdepand{e}% + % and + \def\@nwlangdepuss{Usa}% + % Uses + \def\@nwlangdepusd{usado}% + % used + \def\@nwlangdepnvu{nunca usado}% + % never used + \def\@nwlangdepdfs{Define}% + % Defines + \def\@nwlangdepnvd{nunca definido}% + % never defined +} +\def\nwopt@frenchb{% + \def\@nwlangdepdef{Suite de la d\'efinition}% + % This definition is continued + \def\@nwlangdepcud{Ce code est employ\'e}% + % This code is used + \def\@nwlangdeprtc{Morceau racine (pas employ\'e dans ce document)}% + % Root chunk (not used in this document) + \def\@nwlangdepcwf{Ce code est \'ecrit dans le fichier}% + % This code is written to file + \def\@nwlangdepchk{le morceau}% + % chunk + \def\@nwlangdepchks{les morceaux}% + % chunks + \def\@nwlangdepin{dans}% + % in + \def\@nwlangdepand{et}% + % and + \def\@nwlangdepuss{Utilise}% + % Uses + \def\@nwlangdepusd{utilis\'{e}}% + % used + \def\@nwlangdepnvu{jamais employ\'{e}}% + % never used + \def\@nwlangdepdfs{D\'{e}finit}% + % Defines + % Cannot use the accent here: \def\@nwlangdepnvd{jamais d\'{e}fini}% + \def\@nwlangdepnvd{jamais defini}% + % never defined +} +\let\nwopt@french\nwopt@frenchb +\def\nwopt@german{% + \def\@nwlangdepdef{Diese Definition wird fortgesetzt}% + % This definition is continued + \def\@nwlangdepcud{Dieser Code wird benutzt}% + % This code is used + \def\@nwlangdeprtc{Hauptteil (nicht in diesem Dokument benutzt)}% + % Root chunk (not used in this document) + %\def\@nwlangdepcwf{Dieser Code schreibt man zum File}% + \def\@nwlangdepcwf{Dieser Code geht in Datei}% + % This code is written to file + \def\@nwlangdepchk{dem Teil}% + % chunk + %\def\@nwlangdepchks{Teils}% + \def\@nwlangdepchks{den Teilen}% + % chunks + \def\@nwlangdepin{in}% + % in + \def\@nwlangdepand{und}% + % and + \def\@nwlangdepuss{Benutzt}% + % Uses + \def\@nwlangdepusd{benutzt}% + % used + \def\@nwlangdepnvu{nicht benutzt}% + % never used + \def\@nwlangdepdfs{Definiert}% + % Defines + \def\@nwlangdepnvd{nicht definiert}% + % never defined +} +\def\nwopt@german{% + \def\@nwlangdepdef{Diese Definition wird fortgesetzt}% + % This definition is continued + \def\@nwlangdepcud{Dieser Code wird benutzt}% + % This code is used + \def\@nwlangdeprtc{Hauptteil (nicht in diesem Dokument benutzt)}% + % Root chunk (not used in this document) + \def\@nwlangdepcwf{Dieser Code schreibt man zum File}% + % This code is written to file + \def\@nwlangdepchk{Teil}% + % chunk + \def\@nwlangdepchks{Teils}% + % chunks + \def\@nwlangdepin{im}% + % in + \def\@nwlangdepand{und}% + % and + \def\@nwlangdepuss{Benutzt}% + % Uses + \def\@nwlangdepusd{benutzt}% + % used + \def\@nwlangdepnvu{nicht benutzt}% + % never used + \def\@nwlangdepdfs{Definiert}% + % Defines + \def\@nwlangdepnvd{nicht definiert}% + % never defined +} +\let\nwopt@ngerman\nwopt@german +\ifx\languagename\undefined % default is English + \noweboptions{english} +\else + \@ifundefined{nwopt@\languagename} + {\noweboptions{english}} + {\expandafter\noweboptions\expandafter{\languagename}} +\fi +\let\obeyedspace\@xobeysp diff --git a/peat b/peat new file mode 100644 index 0000000..5078c80 --- /dev/null +++ b/peat @@ -0,0 +1,224 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- + + ############################## + # ____ ___ ____ ______ # + # | \ / _] / T| T # + # | o )/ [_ Y o || | # + # | _/Y _]| |l_j l_j # + # | | | [_ | _ | | | # + # | | | T| | | | | # + # l__j l_____jl__j__j l__j # + # # + ##### ##### + # Repeat commands! # + ################## + +import errno, os, subprocess, sys, time +from optparse import OptionParser + + +interval = 1.0 +command = 'true' +clear = True +get_paths = lambda: set() +verbose = True +dynamic = None +last_run = None + + +USAGE = r"""usage: %prog [options] COMMAND + +COMMAND should be given as a single argument using a shell string. + +A list of paths to watch should be piped in on standard input. + +For example: + + find . | peat './test.sh' + find . -name '*.py' | peat 'rm *.pyc' + find . -name '*.py' -print0 | peat -0 'rm *.pyc' + +If --dynamic is used, the given command will be run each time to generate the +list of files to check: + + peat --dynamic 'find .' './test.sh' + peat --dynamic 'find . -name '\''*.py'\''' 'rm *.pyc' +""" + + +def log(s): + if verbose: + print(s) + +def die(s): + sys.stderr.write('ERROR: ' + s + '\n') + sys.exit(1) + +def check(paths): + for p in paths: + try: + if os.stat(p).st_mtime >= last_run: + return True + except OSError as e: + # If the file has been deleted since we started watching, don't + # worry about it. + if e.errno == errno.ENOENT: + pass + else: + raise + return False + +def run(): + global last_run + last_run = time.time() + log("running: " + command) + subprocess.call(command, shell=True) + +def build_option_parser(): + p = OptionParser(USAGE) + + # Main options + p.add_option('-i', '--interval', default=None, + help='interval between checks in milliseconds', + metavar='N') + p.add_option('-I', '--smart-interval', dest='interval', + action='store_const', const=None, + help='determine the interval based on number of files watched (default)') + + p.add_option('-d', '--dynamic', default=None, + help='run COMMAND before each run to generate the list of files to check', + metavar='COMMAND') + p.add_option('-D', '--no-dynamic', dest='dynamic', + action='store_const', const=None, + help='take a list of files to watch on standard in (default)') + + p.add_option('-c', '--clear', default=True, + action='store_true', dest='clear', + help='clear screen before runs (default)') + p.add_option('-C', '--no-clear', + action='store_false', dest='clear', + help="don't clear screen before runs") + + p.add_option('-v', '--verbose', default=True, + action='store_true', dest='verbose', + help='show extra logging output (default)') + p.add_option('-q', '--quiet', + action='store_false', dest='verbose', + help="don't show extra logging output") + + p.add_option('-w', '--whitespace', default=None, + action='store_const', dest='sep', const=None, + help="assume paths are separated by whitespace (default)") + p.add_option('-n', '--newlines', + action='store_const', dest='sep', const='\n', + help="assume paths are separated by newlines") + p.add_option('-s', '--spaces', + action='store_const', dest='sep', const=' ', + help="assume paths are separated by spaces") + p.add_option('-0', '--zero', + action='store_const', dest='sep', const='\0', + help="assume paths are separated by null bytes") + + return p + + +def _main(): + if dynamic: + log("Running the following command to generate watch list:") + log(' ' + dynamic) + log('') + + log("Watching the following paths:") + for p in get_paths(): + log(' ' + p) + log('') + log('Checking for changes every %d milliseconds.' % int(interval * 1000)) + log('') + + run() + + while True: + time.sleep(interval) + if check(get_paths()): + if clear: + subprocess.check_call('clear') + run() + +def smart_interval(count): + """Return the smart interval to use in milliseconds.""" + if count >= 50: + return 1000 + else: + sq = lambda n: n * n + return int(1000 * (1 - (sq(50.0 - count) / sq(50)))) + +def _parse_interval(options): + global get_paths + if options.interval: + i = int(options.interval) + elif options.dynamic: + i = 1000 + else: + i = smart_interval(len(get_paths())) + + return i / 1000.0 + +def _parse_paths(sep, data): + if not sep: + paths = data.split() + else: + paths = data.split(sep) + + paths = [p.rstrip('\n') for p in paths if p] + paths = map(os.path.abspath, paths) + paths = set(paths) + + return paths + +def main(): + global interval, command, clear, get_paths, verbose, dynamic + + (options, args) = build_option_parser().parse_args() + + if len(args) != 1: + die("exactly one command must be given") + + command = args[0] + clear = options.clear + verbose = options.verbose + sep = options.sep + dynamic = options.dynamic + + if dynamic: + def _get_paths(): + data = subprocess.check_output(dynamic, shell=True) + return _parse_paths(sep, data) + + get_paths = _get_paths + else: + data = sys.stdin.read() + paths = _parse_paths(sep, data) + + if not paths: + die("no paths to watch were given on standard input") + + for path in paths: + if not os.path.exists(path): + die('path to watch does not exist: ' + repr(path)) + + get_paths = lambda: paths + + interval = _parse_interval(options) + + _main() + + +if __name__ == '__main__': + import signal + def sigint_handler(signal, frame): + sys.stdout.write('\n') + sys.exit(130) + signal.signal(signal.SIGINT, sigint_handler) + main() +