changeset 4:33a2e7b9978d

Make it possible to include example input and output from real programs. Instead of having to cut and paste example text, the task is automated.
author Bryan O'Sullivan <bos@serpentine.com>
date Sun, 25 Jun 2006 22:04:50 -0700
parents 906d9021f9e5
children faa29ca23fc8
files .hgignore en/99defs.tex en/Makefile en/examples/mq.qinit-help en/examples/run-example en/mq.tex
diffstat 6 files changed, 96 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sat Jun 24 17:42:40 2006 -0700
+++ b/.hgignore	Sun Jun 25 22:04:50 2006 -0700
@@ -3,6 +3,7 @@
 
 syntax: glob
 
+.run
 .*.swp
 *~
 *.aux
--- a/en/99defs.tex	Sat Jun 24 17:42:40 2006 -0700
+++ b/en/99defs.tex	Sun Jun 25 22:04:50 2006 -0700
@@ -1,10 +1,11 @@
-\newcommand{\tildefile}[1]{\texttt{\~/#1}}
+\newcommand{\tildefile}[1]{\texttt{\~{}/#1}}
 \newcommand{\filename}[1]{\texttt{#1}}
 \newcommand{\hgext}[1]{\texttt{#1}}
 \newcommand{\hgcmd}[1]{``\texttt{hg #1}''}
 \newcommand{\hgcmdargs}[2]{``\texttt{hg #1 #2}''}
 
-\DefineVerbatimEnvironment{codesample}{Verbatim}{frame=single,gobble=2,numbers=left}
+\DefineVerbatimEnvironment{codesample4}{Verbatim}{frame=single,gobble=4,numbers=left,commandchars=\\\{\}}
+\newcommand{\interaction}[1]{\VerbatimInput[frame=single,numbers=left,commandchars=\\\{\}]{examples/#1.out}}
 
 %%% Local Variables: 
 %%% mode: latex
--- a/en/Makefile	Sat Jun 24 17:42:40 2006 -0700
+++ b/en/Makefile	Sun Jun 25 22:04:50 2006 -0700
@@ -4,6 +4,10 @@
 	99defs.tex \
 	mq.tex
 
+example-sources := \
+	examples/run-example \
+	examples/mq.qinit-help
+
 latex-options = \
 	-interaction batchmode \
 	-output-directory $(dir $(1)) \
@@ -13,7 +17,7 @@
 
 pdf: pdf/hgbook.pdf
 
-pdf/hgbook.pdf: $(sources)
+pdf/hgbook.pdf: $(sources) examples
 	mkdir -p $(dir $@)
 	pdflatex $(call latex-options,$@) $< || (rm -f $@; exit 1)
 	cp 99book.bib $(dir $@)
@@ -32,11 +36,18 @@
 	cd $(dir $(1)) && t4ht -f/$(basename $(notdir $(1)))
 endef
 
-html/onepage/hgbook.html: $(sources)
+html/onepage/hgbook.html: $(sources) examples
 	$(call htlatex,$@,$<)
 
-html/split/hgbook.html: $(sources)
+html/split/hgbook.html: $(sources) examples
 	$(call htlatex,$@,$<,2)
 
+.PHONY: examples
+
+examples: examples/.run
+
+examples/.run: $(example-sources)
+	cd examples && ./run-example
+
 clean:
 	rm -rf html pdf *.aux *.dvi *.log *.out
--- a/en/examples/mq.qinit-help	Sat Jun 24 17:42:40 2006 -0700
+++ b/en/examples/mq.qinit-help	Sun Jun 25 22:04:50 2006 -0700
@@ -1,2 +1,2 @@
-# name: help
+#$ name: help
 hg help qinit
--- a/en/examples/run-example	Sat Jun 24 17:42:40 2006 -0700
+++ b/en/examples/run-example	Sun Jun 25 22:04:50 2006 -0700
@@ -1,16 +1,36 @@
 #!/usr/bin/python
+#
+# This program takes something that resembles a shell script and runs
+# it, spitting input (commands from the script) and output into text
+# files, for use in examples.
 
 import cStringIO
 import os
 import pty
 import re
+import shutil
 import sys
+import tempfile
+import time
 
+def tex_escape(s):
+    if '\\' in s:
+        s = s.replace('\\', '\\\\')
+    if '{' in s:
+        s = s.replace('{', '\\{')
+    if '}' in s:
+        s = s.replace('}', '\\}')
+    return s
+        
 class example:
+    shell = '/bin/bash'
+    pi_re = re.compile('#\$\s*(name):\s*(.*)$')
+    
     def __init__(self, name):
         self.name = name
 
     def parse(self):
+        '''yield each hunk of input from the file.'''
         fp = open(self.name)
         cfp = cStringIO.StringIO()
         for line in fp:
@@ -20,28 +40,54 @@
                 cfp.seek(0)
                 cfp.truncate()
         
-    name_re = re.compile('#\s*name:\s*(.*)$')
-    
     def status(self, s):
         sys.stdout.write(s)
         if not s.endswith('\n'):
             sys.stdout.flush()
 
+    def drain(self, ifp, ofp):
+        while True:
+            s = ifp.read(4096)
+            if not s: break
+            if ofp: ofp.write(tex_escape(s))
+        
     def run(self):
         ofp = None
-        self.status('running %s ' % os.path.basename(self.name))
-        for hunk in self.parse():
-            m = self.name_re.match(hunk)
-            if m:
-                self.status('.')
-                out = m.group(1)
-                assert os.sep not in out
-                if out:
-                    ofp = open('%s.%s.out' % (self.name, out), 'w')
+        basename = os.path.basename(self.name)
+        self.status('running %s ' % basename)
+        tmpdir = tempfile.mkdtemp(prefix=basename)
+        try:
+            for hunk in self.parse():
+                # is this line a processing instruction?
+                m = self.pi_re.match(hunk)
+                if m:
+                    pi, rest = m.groups()
+                    if pi == 'name':
+                        self.status('.')
+                        out = rest
+                        assert os.sep not in out
+                        if out:
+                            ofp = open('%s.%s.out' % (self.name, out), 'w')
+                        else:
+                            ofp = None
                 else:
-                    ofp = None
-            elif ofp: ofp.write(hunk)
-        self.status('\n')
+                    # it's something we should execute
+                    cin, cout = os.popen4('cd %s; %s' % (tmpdir, hunk))
+                    cin.close()
+                    if ofp:
+                        # first, print the command we ran
+                        if not hunk.startswith('#'):
+                            nl = hunk.endswith('\n')
+                            hunk = ('$ \\textbf{%s}' %
+                                    tex_escape(hunk.rstrip('\n')))
+                            if nl: hunk += '\n'
+                        ofp.write(hunk)
+                    # then its output
+                    self.drain(cout, ofp)
+            self.status('\n')
+        finally:
+            os.wait()
+            shutil.rmtree(tmpdir)
 
 def main(path='.'):
     args = sys.argv[1:]
@@ -53,6 +99,7 @@
         if name == 'run-example' or name.startswith('.'): continue
         if name.endswith('.out') or name.endswith('~'): continue
         example(os.path.join(path, name)).run()
+    print >> open(os.path.join(path, '.run'), 'w'), time.asctime()
 
 if __name__ == '__main__':
     main()
--- a/en/mq.tex	Sat Jun 24 17:42:40 2006 -0700
+++ b/en/mq.tex	Sun Jun 25 22:04:50 2006 -0700
@@ -131,16 +131,26 @@
 Because MQ is implemented as an extension, you must explicitly enable
 before you can use it.  (You don't need to download anything; MQ ships
 with the standard Mercurial distribution.)  To enable MQ, edit your
-\tildefile{.hgrc} file, and add the following lines:
+\tildefile{.hgrc} file, and add the lines in figure~\ref{ex:mq:config}.
 
-\begin{codesample}
-  [extensions]
-  hgext.mq =
-\end{codesample}
+\begin{figure}
+  \begin{codesample4}
+    [extensions]
+    hgext.mq =
+  \end{codesample4}
+  \label{ex:mq:config}
+  \caption{Contents to add to \tildefile{.hgrc} to enable the MQ extension}
+\end{figure}
 
 Once the extension is enabled, it will make a number of new commands
-available.  
+available.  To verify that the extension is working, follow the
+example in figure~\ref{ex:mq:enabled}.
 
+\begin{figure}
+  \interaction{mq.qinit-help.help}
+  \caption{How to verify that MQ is enabled}
+  \label{ex:mq:enabled}
+\end{figure}
 
 %%% Local Variables: 
 %%% mode: latex