hgbook

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 Jun 25 22:04:50 2006 -0700 (2006-06-25)
parents 906d9021f9e5
children faa29ca23fc8
files .hgignore en/99defs.tex en/Makefile en/examples/mq.qinit-help en/examples/run-example en/mq.tex
line diff
     1.1 --- a/.hgignore	Sat Jun 24 17:42:40 2006 -0700
     1.2 +++ b/.hgignore	Sun Jun 25 22:04:50 2006 -0700
     1.3 @@ -3,6 +3,7 @@
     1.4  
     1.5  syntax: glob
     1.6  
     1.7 +.run
     1.8  .*.swp
     1.9  *~
    1.10  *.aux
     2.1 --- a/en/99defs.tex	Sat Jun 24 17:42:40 2006 -0700
     2.2 +++ b/en/99defs.tex	Sun Jun 25 22:04:50 2006 -0700
     2.3 @@ -1,10 +1,11 @@
     2.4 -\newcommand{\tildefile}[1]{\texttt{\~/#1}}
     2.5 +\newcommand{\tildefile}[1]{\texttt{\~{}/#1}}
     2.6  \newcommand{\filename}[1]{\texttt{#1}}
     2.7  \newcommand{\hgext}[1]{\texttt{#1}}
     2.8  \newcommand{\hgcmd}[1]{``\texttt{hg #1}''}
     2.9  \newcommand{\hgcmdargs}[2]{``\texttt{hg #1 #2}''}
    2.10  
    2.11 -\DefineVerbatimEnvironment{codesample}{Verbatim}{frame=single,gobble=2,numbers=left}
    2.12 +\DefineVerbatimEnvironment{codesample4}{Verbatim}{frame=single,gobble=4,numbers=left,commandchars=\\\{\}}
    2.13 +\newcommand{\interaction}[1]{\VerbatimInput[frame=single,numbers=left,commandchars=\\\{\}]{examples/#1.out}}
    2.14  
    2.15  %%% Local Variables: 
    2.16  %%% mode: latex
     3.1 --- a/en/Makefile	Sat Jun 24 17:42:40 2006 -0700
     3.2 +++ b/en/Makefile	Sun Jun 25 22:04:50 2006 -0700
     3.3 @@ -4,6 +4,10 @@
     3.4  	99defs.tex \
     3.5  	mq.tex
     3.6  
     3.7 +example-sources := \
     3.8 +	examples/run-example \
     3.9 +	examples/mq.qinit-help
    3.10 +
    3.11  latex-options = \
    3.12  	-interaction batchmode \
    3.13  	-output-directory $(dir $(1)) \
    3.14 @@ -13,7 +17,7 @@
    3.15  
    3.16  pdf: pdf/hgbook.pdf
    3.17  
    3.18 -pdf/hgbook.pdf: $(sources)
    3.19 +pdf/hgbook.pdf: $(sources) examples
    3.20  	mkdir -p $(dir $@)
    3.21  	pdflatex $(call latex-options,$@) $< || (rm -f $@; exit 1)
    3.22  	cp 99book.bib $(dir $@)
    3.23 @@ -32,11 +36,18 @@
    3.24  	cd $(dir $(1)) && t4ht -f/$(basename $(notdir $(1)))
    3.25  endef
    3.26  
    3.27 -html/onepage/hgbook.html: $(sources)
    3.28 +html/onepage/hgbook.html: $(sources) examples
    3.29  	$(call htlatex,$@,$<)
    3.30  
    3.31 -html/split/hgbook.html: $(sources)
    3.32 +html/split/hgbook.html: $(sources) examples
    3.33  	$(call htlatex,$@,$<,2)
    3.34  
    3.35 +.PHONY: examples
    3.36 +
    3.37 +examples: examples/.run
    3.38 +
    3.39 +examples/.run: $(example-sources)
    3.40 +	cd examples && ./run-example
    3.41 +
    3.42  clean:
    3.43  	rm -rf html pdf *.aux *.dvi *.log *.out
     4.1 --- a/en/examples/mq.qinit-help	Sat Jun 24 17:42:40 2006 -0700
     4.2 +++ b/en/examples/mq.qinit-help	Sun Jun 25 22:04:50 2006 -0700
     4.3 @@ -1,2 +1,2 @@
     4.4 -# name: help
     4.5 +#$ name: help
     4.6  hg help qinit
     5.1 --- a/en/examples/run-example	Sat Jun 24 17:42:40 2006 -0700
     5.2 +++ b/en/examples/run-example	Sun Jun 25 22:04:50 2006 -0700
     5.3 @@ -1,16 +1,36 @@
     5.4  #!/usr/bin/python
     5.5 +#
     5.6 +# This program takes something that resembles a shell script and runs
     5.7 +# it, spitting input (commands from the script) and output into text
     5.8 +# files, for use in examples.
     5.9  
    5.10  import cStringIO
    5.11  import os
    5.12  import pty
    5.13  import re
    5.14 +import shutil
    5.15  import sys
    5.16 +import tempfile
    5.17 +import time
    5.18  
    5.19 +def tex_escape(s):
    5.20 +    if '\\' in s:
    5.21 +        s = s.replace('\\', '\\\\')
    5.22 +    if '{' in s:
    5.23 +        s = s.replace('{', '\\{')
    5.24 +    if '}' in s:
    5.25 +        s = s.replace('}', '\\}')
    5.26 +    return s
    5.27 +        
    5.28  class example:
    5.29 +    shell = '/bin/bash'
    5.30 +    pi_re = re.compile('#\$\s*(name):\s*(.*)$')
    5.31 +    
    5.32      def __init__(self, name):
    5.33          self.name = name
    5.34  
    5.35      def parse(self):
    5.36 +        '''yield each hunk of input from the file.'''
    5.37          fp = open(self.name)
    5.38          cfp = cStringIO.StringIO()
    5.39          for line in fp:
    5.40 @@ -20,28 +40,54 @@
    5.41                  cfp.seek(0)
    5.42                  cfp.truncate()
    5.43          
    5.44 -    name_re = re.compile('#\s*name:\s*(.*)$')
    5.45 -    
    5.46      def status(self, s):
    5.47          sys.stdout.write(s)
    5.48          if not s.endswith('\n'):
    5.49              sys.stdout.flush()
    5.50  
    5.51 +    def drain(self, ifp, ofp):
    5.52 +        while True:
    5.53 +            s = ifp.read(4096)
    5.54 +            if not s: break
    5.55 +            if ofp: ofp.write(tex_escape(s))
    5.56 +        
    5.57      def run(self):
    5.58          ofp = None
    5.59 -        self.status('running %s ' % os.path.basename(self.name))
    5.60 -        for hunk in self.parse():
    5.61 -            m = self.name_re.match(hunk)
    5.62 -            if m:
    5.63 -                self.status('.')
    5.64 -                out = m.group(1)
    5.65 -                assert os.sep not in out
    5.66 -                if out:
    5.67 -                    ofp = open('%s.%s.out' % (self.name, out), 'w')
    5.68 +        basename = os.path.basename(self.name)
    5.69 +        self.status('running %s ' % basename)
    5.70 +        tmpdir = tempfile.mkdtemp(prefix=basename)
    5.71 +        try:
    5.72 +            for hunk in self.parse():
    5.73 +                # is this line a processing instruction?
    5.74 +                m = self.pi_re.match(hunk)
    5.75 +                if m:
    5.76 +                    pi, rest = m.groups()
    5.77 +                    if pi == 'name':
    5.78 +                        self.status('.')
    5.79 +                        out = rest
    5.80 +                        assert os.sep not in out
    5.81 +                        if out:
    5.82 +                            ofp = open('%s.%s.out' % (self.name, out), 'w')
    5.83 +                        else:
    5.84 +                            ofp = None
    5.85                  else:
    5.86 -                    ofp = None
    5.87 -            elif ofp: ofp.write(hunk)
    5.88 -        self.status('\n')
    5.89 +                    # it's something we should execute
    5.90 +                    cin, cout = os.popen4('cd %s; %s' % (tmpdir, hunk))
    5.91 +                    cin.close()
    5.92 +                    if ofp:
    5.93 +                        # first, print the command we ran
    5.94 +                        if not hunk.startswith('#'):
    5.95 +                            nl = hunk.endswith('\n')
    5.96 +                            hunk = ('$ \\textbf{%s}' %
    5.97 +                                    tex_escape(hunk.rstrip('\n')))
    5.98 +                            if nl: hunk += '\n'
    5.99 +                        ofp.write(hunk)
   5.100 +                    # then its output
   5.101 +                    self.drain(cout, ofp)
   5.102 +            self.status('\n')
   5.103 +        finally:
   5.104 +            os.wait()
   5.105 +            shutil.rmtree(tmpdir)
   5.106  
   5.107  def main(path='.'):
   5.108      args = sys.argv[1:]
   5.109 @@ -53,6 +99,7 @@
   5.110          if name == 'run-example' or name.startswith('.'): continue
   5.111          if name.endswith('.out') or name.endswith('~'): continue
   5.112          example(os.path.join(path, name)).run()
   5.113 +    print >> open(os.path.join(path, '.run'), 'w'), time.asctime()
   5.114  
   5.115  if __name__ == '__main__':
   5.116      main()
     6.1 --- a/en/mq.tex	Sat Jun 24 17:42:40 2006 -0700
     6.2 +++ b/en/mq.tex	Sun Jun 25 22:04:50 2006 -0700
     6.3 @@ -131,16 +131,26 @@
     6.4  Because MQ is implemented as an extension, you must explicitly enable
     6.5  before you can use it.  (You don't need to download anything; MQ ships
     6.6  with the standard Mercurial distribution.)  To enable MQ, edit your
     6.7 -\tildefile{.hgrc} file, and add the following lines:
     6.8 +\tildefile{.hgrc} file, and add the lines in figure~\ref{ex:mq:config}.
     6.9  
    6.10 -\begin{codesample}
    6.11 -  [extensions]
    6.12 -  hgext.mq =
    6.13 -\end{codesample}
    6.14 +\begin{figure}
    6.15 +  \begin{codesample4}
    6.16 +    [extensions]
    6.17 +    hgext.mq =
    6.18 +  \end{codesample4}
    6.19 +  \label{ex:mq:config}
    6.20 +  \caption{Contents to add to \tildefile{.hgrc} to enable the MQ extension}
    6.21 +\end{figure}
    6.22  
    6.23  Once the extension is enabled, it will make a number of new commands
    6.24 -available.  
    6.25 +available.  To verify that the extension is working, follow the
    6.26 +example in figure~\ref{ex:mq:enabled}.
    6.27  
    6.28 +\begin{figure}
    6.29 +  \interaction{mq.qinit-help.help}
    6.30 +  \caption{How to verify that MQ is enabled}
    6.31 +  \label{ex:mq:enabled}
    6.32 +\end{figure}
    6.33  
    6.34  %%% Local Variables: 
    6.35  %%% mode: latex