testLeoAtFile.py :  » Development » Leo » Leo-4.7.1-final » leo » test » unittest » input » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Development » Leo 
Leo » Leo 4.7.1 final » leo » test » unittest » input » testLeoAtFile.py
#@+leo-ver=4
#@+node:@file leoAtFile.py 
"""Classes to read and write @file nodes."""

#@@language python

from leoGlobals import *
import leoColor,leoNodes
import filecmp,os,time

#@<< global atFile constants >>
#@+node:<< global atFile constants >>
# These constants must be global to this module because they are shared by several classes.

# The kind of at_directives.
noDirective       =  1 # not an at-directive.
# not used      =  2
docDirective     =  3 # @doc.
atDirective       =  4 # @<space> or @<newline>
codeDirective    =  5 # @code
cDirective        =  6 # @c<space> or @c<newline>
othersDirective  =  7 # at-others
miscDirective    =  8 # All other directives
rawDirective    =  9 # @raw
endRawDirective = 10 # @end_raw

# The kind of sentinel line.
noSentinel     = 20 # Not a sentinel
# not used   = 21
endAt       = 22 # @-at
endBody       = 23 # @-body
endDoc       = 24 # @-doc
endLeo       = 25 # @-leo
endNode       = 26 # @-node
endOthers      = 27 # @-others

# not used     = 40
startAt         = 41 # @+at
startBody        = 42 # @+body
startDoc         = 43 # @+doc
startLeo         = 44 # @+leo
startNode        = 45 # @+node
startOthers      = 46 # @+others

startComment   = 60 # @comment
startDelims      = 61 # @delims
startDirective  = 62 # @@
startRef         = 63 # @< < ... > >
startVerbatim   = 64 # @verbatim
startVerbatimAfterRef = 65 # @verbatimAfterRef (3.0 only)

# New in 4.0...
startAfterRef  = 70 # @afterref (4.0)
startNl        = 71 # @nl (4.0)
startNonl      = 72 # @nonl (4.0)
  
sentinelDict = {
  # Unpaired sentinels: 3.x and 4.x.
  "@comment" : startComment,
  "@delims" :  startDelims,
  "@verbatim": startVerbatim,
  # Unpaired sentinels: 3.x only.
  "@verbatimAfterRef": startVerbatimAfterRef,
  # Unpaired sentinels: 4.x only.
  "@afterref" : startAfterRef,
  "@nl"       : startNl,
  "@nonl"     : startNonl,
  # Paired sentinels: 3.x only.
  "@+body":   startBody,   "@-body":   endBody,
  # Paired sentinels: 3.x and 4.x.
  "@+at":     startAt,     "@-at":     endAt,
  "@+doc":    startDoc,    "@-doc":    endDoc,
  "@+leo":    startLeo,    "@-leo":    endLeo,
  "@+node":   startNode,   "@-node":   endNode,
  "@+others": startOthers, "@-others": endOthers }
#@nonl
#@-node:<< global atFile constants >>
#@nl

class baseAtFile:
  """The base class for the top-level atFile subcommander."""
  #@  << class baseAtFile methods >>
  #@+node:<< class baseAtFile methods >>
  #@+others
  #@+node:atFile.__init__ & initIvars
  def __init__(self,c):
    
    self.c = c
    self.fileCommands = self.c.fileCommands
    
    # Create subcommanders to handler old and new format derived files.
    self.old_df = oldDerivedFile(c)
    self.new_df = newDerivedFile(c)
    
    self.initIvars()
    
  def initIvars(self):
    
    # Set by scanDefaultDirectory.
    self.default_directory = None
    self.errors = 0
  
    # Set by scanHeader when reading. Set by scanAllDirectives...
    self.encoding = app.config.default_derived_file_encoding
    self.endSentinelComment = None
    self.startSentinelComment = None
  #@nonl
  #@-node:atFile.__init__ & initIvars
  #@+node:top_df.error
  def error(self,message):
  
    es(message,color="red")
    print message
    self.errors += 1
  #@nonl
  #@-node:top_df.error
  #@+node: top_df.readAll
  def readAll(self,root,partialFlag=false):
    
    """Scan vnodes, looking for @file nodes to read."""
  
    at = self ; c = at.c
    c.endEditing() # Capture the current headline.
    anyRead = false
    at.initIvars()
    v = root
    if partialFlag: after = v.nodeAfterTree()
    else: after = None
    while v and v != after:
      if v.isAtIgnoreNode():
        v = v.nodeAfterTree()
      elif v.isAtFileNode() or v.isAtRawFileNode():
        anyRead = true
        if partialFlag:
          # We are forcing the read.
          at.read(v)
        else:
          # if v is an orphan, we don't expect to see a derived file,
          # and we shall read a derived file if it exists.
          wasOrphan = v.isOrphan()
          ok = at.read(v)
          if wasOrphan and not ok:
            # Remind the user to fix the problem.
            v.setDirty()
            c.setChanged(true)
        v = v.nodeAfterTree()
      else: v = v.threadNext()
    # Clear all orphan bits.
    v = root
    while v:
      v.clearOrphan()
      v = v.threadNext()
      
    if partialFlag and not anyRead:
      es("no @file nodes in the selected tree")
  #@nonl
  #@-node: top_df.readAll
  #@+node:top_df.read
  # The caller has enclosed this code in beginUpdate/endUpdate.
  
  def read(self,root,importFileName=None):
    
    """Common read logic for any derived file."""
    
    at = self ; c = at.c
    at.errors = 0
    at.scanDefaultDirectory(root)
    if at.errors: return
    #@  << set fileName from root and importFileName >>
    #@+node:<< set fileName from root and importFileName >>
    if importFileName:
      fileName = importFileName
    elif root.isAtFileNode():
      fileName = root.atFileNodeName()
    else:
      fileName = root.atRawFileNodeName()
      
    if not fileName:
      at.error("Missing file name.  Restoring @file tree from .leo file.")
      return false
    #@nonl
    #@-node:<< set fileName from root and importFileName >>
    #@nl
    #@  << open file or return false >>
    #@+node:<< open file or return false >>
    fn = os_path_join(at.default_directory,fileName)
    fn = os_path_normpath(fn)
    
    try:
      # 11/4/03: open the file in binary mode to allow 0x1a in bodies & headlines.
      file = open(fn,'rb')
      if file:
        #@    << warn on read-only file >>
        #@+node:<< warn on read-only file >>
        try:
          read_only = not os.access(fn,os.W_OK)
          if read_only:
            es("read only: " + fn,color="red")
        except:
          pass # os.access() may not exist on all platforms.
        #@nonl
        #@-node:<< warn on read-only file >>
        #@nl
      else: return false
    except:
      at.error("Can not open: " + '"@file ' + fn + '"')
      root.setDirty()
      return false
    #@nonl
    #@-node:<< open file or return false >>
    #@nl
    es("reading: " + root.headString())
    firstLines,read_new = at.scanHeader(file,fileName)
    df = choose(read_new,at.new_df,at.old_df)
    #@  << copy ivars to df >>
    #@+node:<< copy ivars to df >>
    df.importing = importFileName != None
    df.importRootSeen = false
    
    # Set by scanHeader.
    df.encoding = at.encoding
    df.endSentinelComment = at.endSentinelComment
    df.startSentinelComment = at.startSentinelComment
    
    # Set other common ivars.
    df.errors = 0
    df.file = file
    df.targetFileName = fileName
    df.indent = 0
    df.raw = false
    df.root = root
    df.root_seen = false
    #@-node:<< copy ivars to df >>
    #@nl
    root.clearVisitedInTree()
    try:
      df.readOpenFile(root,file,firstLines)
    except:
      at.error("Unexpected exception while reading derived file")
      es_exception()
    file.close()
    root.clearDirty() # May be set dirty below.
    after = root.nodeAfterTree()
    #@  << warn about non-empty unvisited nodes >>
    #@+node:<< warn about non-empty unvisited nodes >>
    v = root
    while v and v != after:
      try: s = v.t.tempBodyString
      except: s = ""
      if s and not v.t.isVisited():
        at.error("Not in derived file:" + v.headString())
        v.t.setVisited() # One message is enough.
      v = v.threadNext()
    #@nonl
    #@-node:<< warn about non-empty unvisited nodes >>
    #@nl
    if df.errors == 0:
      if not df.importing:
        #@      << copy all tempBodyStrings to tnodes >>
        #@+node:<< copy all tempBodyStrings to tnodes >>
        v = root
        while v and v != after:
          try: s = v.t.tempBodyString
          except: s = ""
          if s != v.bodyString():
            es("changed: " + v.headString(),color="blue")
            if 0: # For debugging.
              print ; print "changed: " + v.headString()
              print ; print "new:",`s`
              print ; print "old:",`v.bodyString()`
            v.setBodyStringOrPane(s) # Sets v and v.c dirty.
            v.setMarked()
          v = v.threadNext()
        #@nonl
        #@-node:<< copy all tempBodyStrings to tnodes >>
        #@nl
    #@  << delete all tempBodyStrings >>
    #@+node:<< delete all tempBodyStrings >>
    v = root
    while v and v != after:
      if hasattr(v.t,"tempBodyString"):
        delattr(v.t,"tempBodyString")
      v = v.threadNext()
    #@nonl
    #@-node:<< delete all tempBodyStrings >>
    #@nl
    return df.errors == 0
  #@nonl
  #@-node:top_df.read
  #@+node:top_df.scanDefaultDirectory
  def scanDefaultDirectory(self,v):
    
    """Set default_directory ivar by looking for @path directives."""
  
    at = self ; c = at.c
    at.default_directory = None
    #@  << Set path from @file node >>
    #@+node:<< Set path from @file node >>  in df.scanDeafaultDirectory in leoAtFile.py
    # An absolute path in an @file node over-rides everything else.
    # A relative path gets appended to the relative path by the open logic.
    
    # Bug fix: 10/16/02
    if v.isAtFileNode():
      name = v.atFileNodeName()
    elif v.isAtRawFileNode():
      name = v.atRawFileNodeName()
    elif v.isAtNoSentinelsFileNode():
      name = v.atNoSentinelsFileNodeName()
    else:
      name = ""
    
    dir = choose(name,os_path_dirname(name),None)
    
    if dir and os_path_isabs(dir):
      if os_path_exists(dir):
        at.default_directory = dir
      else:
        at.default_directory = makeAllNonExistentDirectories(dir)
        if not at.default_directory:
          at.error("Directory \"" + dir + "\" does not exist")
    #@nonl
    #@-node:<< Set path from @file node >>  in df.scanDeafaultDirectory in leoAtFile.py
    #@nl
    if at.default_directory:
      return
  
    while v:
      s = v.t.bodyString
      dict = get_directives_dict(s)
      if dict.has_key("path"):
        #@      << handle @path >>
        #@+node:<< handle @path >> in df.scanDeafaultDirectory in leoAtFile.py
        # We set the current director to a path so future writes will go to that directory.
        
        k = dict["path"]
        #@<< compute relative path from s[k:] >>
        #@+node:<< compute relative path from s[k:] >>
        j = i = k + len("@path")
        i = skip_to_end_of_line(s,i)
        path = string.strip(s[j:i])
        
        # Remove leading and trailing delims if they exist.
        if len(path) > 2 and (
          (path[0]=='<' and path[-1] == '>') or
          (path[0]=='"' and path[-1] == '"') ):
          path = path[1:-1]
        
        path = path.strip()
        #@nonl
        #@-node:<< compute relative path from s[k:] >>
        #@nl
        
        if path and len(path) > 0:
          base = getBaseDirectory() # returns "" on error.
          path = os_path_join(base,path)
          
          if os_path_isabs(path):
            #@    << handle absolute path >>
            #@+node:<< handle absolute path >>
            # path is an absolute path.
            
            if os_path_exists(path):
              at.default_directory = path
            else:
              at.default_directory = makeAllNonExistentDirectories(path)
              if not at.default_directory:
                at.error("invalid @path: " + path)
            #@nonl
            #@-node:<< handle absolute path >>
            #@nl
          else:
            at.error("ignoring bad @path: " + path)
        else:
          at.error("ignoring empty @path")
        
        #@-node:<< handle @path >> in df.scanDeafaultDirectory in leoAtFile.py
        #@nl
        return
      v = v.parent()
  
    #@  << Set current directory >>
    #@+node:<< Set current directory >>
    # This code is executed if no valid absolute path was specified in the @file node or in an @path directive.
    
    assert(not at.default_directory)
    
    if c.frame :
      base = getBaseDirectory() # returns "" on error.
      for dir in (c.tangle_directory,c.frame.openDirectory,c.openDirectory):
        if dir and len(dir) > 0:
          dir = os_path_join(base,dir)
          if os_path_isabs(dir): # Errors may result in relative or invalid path.
            if os_path_exists(dir):
              at.default_directory = dir ; break
            else:
              at.default_directory = makeAllNonExistentDirectories(dir)
    #@-node:<< Set current directory >>
    #@nl
    if not at.default_directory:
      # This should never happen: c.openDirectory should be a good last resort.
      at.error("No absolute directory specified anywhere.")
      at.default_directory = ""
  #@nonl
  #@-node:top_df.scanDefaultDirectory
  #@+node:top_df.scanHeader
  def scanHeader(self,file,fileName):
    
    """Scan the @+leo sentinel.
    
    Sets self.encoding, and self.start/endSentinelComment.
    
    Returns (firstLines,new_df) where:
    firstLines contains all @first lines,
    new_df is true if we are reading a new-format derived file."""
    
    at = self
    new_df = false # Set default.
    firstLines = [] # The lines before @+leo.
    version_tag = "-ver="
    tag = "@+leo" ; encoding_tag = "-encoding="
    valid = true
    #@  << skip any non @+leo lines >>
    #@+node:<< skip any non @+leo lines >>
    #@+at 
    #@nonl
    # Queue up the lines before the @+leo.  These will be used to add as 
    # parameters to the @first directives, if any.  Empty lines are 
    # ignored (because empty @first directives are ignored). NOTE: the 
    # function now returns a list of the lines before @+leo.
    # 
    # We can not call sentinelKind here because that depends on the 
    # comment delimiters we set here.  @first lines are written 
    # "verbatim", so nothing more needs to be done!
    #@-at
    #@@c
    
    s = at.readLine(file)
    while len(s) > 0:
      j = s.find(tag)
      if j != -1: break
      firstLines.append(s) # Queue the line
      s = at.readLine(file)
    n = len(s)
    valid = n > 0
    # s contains the tag
    i = j = skip_ws(s,0)
    # The opening comment delim is the initial non-whitespace.
    # 7/8/02: The opening comment delim is the initial non-tag
    while i < n and not match(s,i,tag) and not is_nl(s,i):
      i += 1
    if j < i:
      at.startSentinelComment = s[j:i]
    else: valid = false
    #@nonl
    #@-node:<< skip any non @+leo lines >>
    #@nl
    #@  << make sure we have @+leo >>
    #@+node:<< make sure we have @+leo >>
    #@+at 
    #@nonl
    # REM hack: leading whitespace is significant before the @+leo.  We do 
    # this so that sentinelKind need not skip whitespace following 
    # self.startSentinelComment.  This is correct: we want to be as 
    # restrictive as possible about what is recognized as a sentinel.  
    # This minimizes false matches.
    #@-at
    #@@c
    
    if 0:# 7/8/02: make leading whitespace significant.
      i = skip_ws(s,i)
    
    if match(s,i,tag):
      i += len(tag)
    else: valid = false
    #@nonl
    #@-node:<< make sure we have @+leo >>
    #@nl
    #@  << read optional version param >>
    #@+node:<< read optional version param >>
    new_df = match(s,i,version_tag)
    
    if new_df:
      # Skip to the next minus sign or end-of-line
      i += len(version_tag)
      j = i
      while i < len(s) and not is_nl(s,i) and s[i] != '-':
        i += 1
    
      if j < i:
        pass # version = s[j:i]
      else:
        valid = false
    #@-node:<< read optional version param >>
    #@nl
    #@  << read optional encoding param >>
    #@+node:<< read optional encoding param >>
    # Set the default encoding
    at.encoding = app.config.default_derived_file_encoding
    
    if match(s,i,encoding_tag):
      # Read optional encoding param, e.g., -encoding=utf-8,
      i += len(encoding_tag)
      # Skip to the next comma
      j = i
      while i < len(s) and not is_nl(s,i) and s[i] not in (',','.'):
        i += 1
      if match(s,i,',') or match(s,i,'.'):
        encoding = s[j:i]
        i += 1
        # print "@+leo-encoding=",encoding
        if isValidEncoding(encoding):
          at.encoding = encoding
        else:
          print "bad encoding in derived file:",encoding
          es("bad encoding in derived file:",encoding)
      else:
        valid = false
    #@-node:<< read optional encoding param >>
    #@nl
    #@  << set the closing comment delim >>
    #@+node:<< set the closing comment delim >>
    # The closing comment delim is the trailing non-whitespace.
    i = j = skip_ws(s,i)
    while i < n and not is_ws(s[i]) and not is_nl(s,i):
      i += 1
    at.endSentinelComment = s[j:i]
    #@nonl
    #@-node:<< set the closing comment delim >>
    #@nl
    if not valid:
      at.error("Bad @+leo sentinel in " + fileName)
    return firstLines, new_df
  #@nonl
  #@-node:top_df.scanHeader
  #@+node:top_df.readLine
  def readLine (self,file):
  
    """Reads one line from file using the present encoding"""
    
    s = readlineForceUnixNewline(file)
    u = toUnicode(s,self.encoding)
    return u
  #@nonl
  #@-node:top_df.readLine
  #@+node:top_df.writeAll
  def writeAll(self,writeAtFileNodesFlag=false,writeDirtyAtFileNodesFlag=false):
    
    """Write @file nodes in all or part of the outline"""
  
    at = self ; c = at.c
    write_new = not app.config.write_old_format_derived_files
    df = choose(write_new,at.new_df,at.old_df)
    df.initIvars()
    changedFiles = [] # Files that were actually changed.
    writtenFiles = [] # Files that might be written again.
  
    if writeAtFileNodesFlag:
      # Write all nodes in the selected tree.
      v = c.currentVnode()
      after = v.nodeAfterTree()
    else:
      # Write dirty nodes in the entire outline.
      v = c.rootVnode()
      after = None
  
    #@  << Clear all orphan bits >>
    #@+node:<< Clear all orphan bits >>
    #@+at 
    #@nonl
    # We must clear these bits because they may have been set on a 
    # previous write.  Calls to atFile::write may set the orphan bits in 
    # @file nodes.  If so, write_LEO_file will write the entire @file 
    # tree.
    #@-at
    #@@c
    
    v2 = v
    while v2 and v2 != after:
      v2.clearOrphan()
      v2 = v2.threadNext()
    #@nonl
    #@-node:<< Clear all orphan bits >>
    #@nl
    while v and v != after:
      if v.isAnyAtFileNode() or v.isAtIgnoreNode():
        #@      << handle v's tree >>
        #@+node:<< handle v's tree >>
        # This code is a little tricky: @ignore not recognised in @silentfile nodes.
        
        if v.isDirty() or writeAtFileNodesFlag or v.t in writtenFiles:
        
          if v.isAtSilentFileNode():
            at.silentWrite(v)
          elif v.isAtIgnoreNode():
            pass
          elif v.isAtRawFileNode():
            at.rawWrite(v)
          elif v.isAtNoSentinelsFileNode():
            at.write(v,nosentinels=true)
          elif v.isAtFileNode():
            at.write(v)
        
          if not v.isAtIgnoreNode():
            writtenFiles.append(v.t)
        
          if df.fileChangedFlag: # Set by replaceTargetFileIfDifferent.
            changedFiles.append(v.t)
        #@nonl
        #@-node:<< handle v's tree >>
        #@nl
        v = v.nodeAfterTree()
      else:
        v = v.threadNext()
  
    #@  << say the command is finished >>
    #@+node:<< say the command is finished >>
    if writeAtFileNodesFlag or writeDirtyAtFileNodesFlag:
      if len(writtenFiles) > 0:
        es("finished")
      elif writeAtFileNodesFlag:
        es("no @file nodes in the selected tree")
      else:
        es("no dirty @file nodes")
    #@nonl
    #@-node:<< say the command is finished >>
    #@nl
    return len(changedFiles) > 0 # So caller knows whether to do an auto-save.
  #@-node:top_df.writeAll
  #@+node:top_df.write, rawWrite, silentWrite
  def rawWrite (self,v):
    at = self
    write_new = not app.config.write_old_format_derived_files
    df = choose(write_new,at.new_df,at.old_df)
    try:    df.rawWrite(v)
    except: at.writeException(v)
    
  def silentWrite (self,v):
    at = self
    try: at.old_df.silentWrite(v) # No new_df.silentWrite method.
    except: at.writeException(v)
    
  def write (self,v,nosentinels=false):
    at = self
    write_new = not app.config.write_old_format_derived_files
    df = choose(write_new,at.new_df,at.old_df)
    try:    df.write(v,nosentinels)
    except: at.writeException(v)
      
  def writeException(self,v):
    self.error("Unexpected exception while writing " + v.headString())
    es_exception()
  #@nonl
  #@-node:top_df.write, rawWrite, silentWrite
  #@+node:top_df.writeOld/NewDerivedFiles
  def writeOldDerivedFiles (self):
    
    self.writeDerivedFiles(write_old=true)
  
  def writeNewDerivedFiles (self):
  
    self.writeDerivedFiles(write_old=false)
    
  def writeDerivedFiles (self,write_old):
    
    config = app.config
    old = config.write_old_format_derived_files
    config.write_old_format_derived_files = write_old
    self.writeAll(writeAtFileNodesFlag=true)
    config.write_old_format_derived_files = old
  #@nonl
  #@-node:top_df.writeOld/NewDerivedFiles
  #@+node:top_df.writeMissing
  def writeMissing(self,v):
  
    at = self
    if at.trace: trace("old_df",v)
  
    write_new = not app.config.write_old_format_derived_files
    df = choose(write_new,at.new_df,at.old_df)
    df.initIvars()
    writtenFiles = false ; changedFiles = false
    after = v.nodeAfterTree()
    while v and v != after:
      if v.isAtSilentFileNode() or (v.isAnyAtFileNode() and not v.isAtIgnoreNode()):
        missing = false ; valid = true
        df.targetFileName = v.anyAtFileNodeName()
        #@      << set missing if the file does not exist >>
        #@+node:<< set missing if the file does not exist >>
        # This is similar, but not the same as, the logic in openWriteFile.
        
        valid = df.targetFileName and len(df.targetFileName) > 0
        
        if valid:
          try:
            # Creates missing directives if option is enabled.
            df.scanAllDirectives(v)
            valid = df.errors == 0
          except:
            es("exception in atFile.scanAllDirectives")
            es_exception()
            valid = false
        
        if valid:
          try:
            fn = df.targetFileName
            df.shortFileName = fn # name to use in status messages.
            df.targetFileName = os_path_join(df.default_directory,fn)
            df.targetFileName = os_path_normpath(df.targetFileName)
        
            path = df.targetFileName # Look for the full name, not just the directory.
            valid = path and len(path) > 0
            if valid:
              missing = not os_path_exists(path)
          except:
            es("exception creating path:" + fn)
            es_exception()
            valid = false
        #@nonl
        #@-node:<< set missing if the file does not exist >>
        #@nl
        if valid and missing:
          #@        << create df.outputFile >>
          #@+node:<< create df.outputFile >>
          try:
            df.outputFileName = df.targetFileName + ".leotmp"
            df.outputFile = open(df.outputFileName,'wb')
            if df.outputFile == None:
              es("can not open " + df.outputFileName)
          except:
            es("exception opening:" + df.outputFileName)
            es_exception()
            df.outputFile = None
          #@-node:<< create df.outputFile >>
          #@nl
          if at.outputFile:
            #@          << write the @file node >>
            #@+node:<< write the @file node >>
            if v.isAtSilentFileNode():
              at.silentWrite(v)
            elif v.isAtRawFileNode():
              at.rawWrite(v)
            elif v.isAtNoSentinelsFileNode():
              at.write(v,nosentinels=true)
            elif v.isAtFileNode():
              at.write(v)
            else: assert(0)
            
            writtenFiles = true
            
            if df.fileChangedFlag: # Set by replaceTargetFileIfDifferent.
              changedFiles = true
            #@nonl
            #@-node:<< write the @file node >>
            #@nl
        v = v.nodeAfterTree()
      elif v.isAtIgnoreNode():
        v = v.nodeAfterTree()
      else:
        v = v.threadNext()
    
    if writtenFiles > 0:
      es("finished")
    else:
      es("no missing @file node in the selected tree")
      
    return changedFiles # So caller knows whether to do an auto-save.
  #@nonl
  #@-node:top_df.writeMissing
  #@-others
  #@nonl
  #@-node:<< class baseAtFile methods >>
  #@nl
  
class atFile (baseAtFile):
  pass # May be overridden in plugins.
  
class baseOldDerivedFile:
  """The base class to read and write 3.x derived files."""
  #@  << class baseOldDerivedFile methods >>
  #@+node:<< class baseOldDerivedFile methods >>
  #@+others
  #@+node: old_df.__init__& initIvars
  def __init__(self,c):
  
    self.c = c # The commander for the current window.
    self.fileCommands = self.c.fileCommands
  
    self.initIvars()
  
  def initIvars(self):
  
    #@  << init atFile ivars >>
    #@+node:<< init atFile ivars >>
    # errors is the number of errors seen while reading and writing.
    self.errors = 0
    
    # Initialized by atFile.scanAllDirectives.
    self.default_directory = None
    self.page_width = None
    self.tab_width  = None
    self.startSentinelComment = None
    self.endSentinelComment = None
    self.language = None
    
    #@+at 
    #@nonl
    # The files used by the output routines.  When tangling, we first 
    # write to a temporary output file.  After tangling is temporary 
    # file.  Otherwise we delete the old target file and rename the 
    # temporary file to be the target file.
    #@-at
    #@@c
    self.shortFileName = "" # short version of file name used for messages.
    self.targetFileName = u"" # EKR 1/21/03: now a unicode string
    self.outputFileName = u"" # EKR 1/21/03: now a unicode string
    self.outputFile = None # The temporary output file.
    
    #@+at 
    #@nonl
    # The indentation used when outputting section references or at-others 
    # sections.  We add the indentation of the line containing the at-node 
    # directive and restore the old value when the
    # expansion is complete.
    #@-at
    #@@c
    self.indent = 0  # The unit of indentation is spaces, not tabs.
    
    # The root of tree being written.
    self.root = None
    
    # Ivars used to suppress newlines between sentinels.
    self.suppress_newlines = true # true: enable suppression of newlines.
    self.newline_pending = false # true: newline is pending on read or write.
    
    # Support of output_newline option
    self.output_newline = getOutputNewline()
    
    # Support of @raw
    self.raw = false # true: in @raw mode
    self.sentinels = true # true: output sentinels while expanding refs.
    
    # Enables tracing (debugging only).
    self.trace = false
    
    # The encoding used to convert from unicode to a byte stream.
    self.encoding = app.config.default_derived_file_encoding
    
    # For interface between 3.x and 4.x read code.
    self.file = None
    self.importing = false
    self.importRootSeen = false
    
    # Set when a file has actually been updated.
    self.fileChangedFlag = false
    #@nonl
    #@-node:<< init atFile ivars >>
    #@nl
  #@-node: old_df.__init__& initIvars
  #@+node:createImportedNode
  def createImportedNode (self,root,c,headline):
    
    at = self
  
    if at.importRootSeen:
      v = root.insertAsLastChild()
      v.initHeadString(headline)
    else:
      # Put the text into the already-existing root node.
      v = root
      at.importRootSeen = true
      
    v.t.setVisited() # Suppress warning about unvisited node.
    return v
  #@nonl
  #@-node:createImportedNode
  #@+node:old_df.readOpenFile
  def readOpenFile(self,root,file,firstLines):
    
    """Read an open 3.x derived file."""
    
    at = self
    if at.trace: trace("old_df",root)
  
    # Scan the file buffer
    at.scanAllDirectives(root)
    lastLines = at.scanText(file,root,[],endLeo)
    root.t.setVisited() # Disable warning about set nodes.
  
    # Handle first and last lines.
    try: body = root.t.tempBodyString
    except: body = ""
    lines = body.split('\n')
    at.completeFirstDirectives(lines,firstLines)
    at.completeLastDirectives(lines,lastLines)
    s = '\n'.join(lines).replace('\r', '')
    root.t.tempBodyString = s
  #@nonl
  #@-node:old_df.readOpenFile
  #@+node:completeFirstDirectives
  # 14-SEP-2002 DTHEIN: added for use by atFile.read()
  
  # this function scans the lines in the list 'out' for @first directives
  # and appends the corresponding line from 'firstLines' to each @first 
  # directive found.  NOTE: the @first directives must be the very first
  # lines in 'out'.
  def completeFirstDirectives(self,out,firstLines):
  
    tag = "@first"
    foundAtFirstYet = 0
    outRange = range(len(out))
    j = 0
    for k in outRange:
      # skip leading whitespace lines
      if (not foundAtFirstYet) and (len(out[k].strip()) == 0): continue
      # quit if something other than @first directive
      i = 0
      if not match(out[k],i,tag): break;
      foundAtFirstYet = 1
      # quit if no leading lines to apply
      if j >= len(firstLines): break
      # make the new @first directive
      #18-SEP-2002 DTHEIN: remove trailing newlines because they are inserted later
      # 21-SEP-2002 DTHEIN: no trailing whitespace on empty @first directive
      leadingLine = " " + firstLines[j]
      out[k] = tag + leadingLine.rstrip() ; j += 1
  #@-node:completeFirstDirectives
  #@+node:completeLastDirectives
  # 14-SEP-2002 DTHEIN: added for use by atFile.read()
  
  # this function scans the lines in the list 'out' for @last directives
  # and appends the corresponding line from 'lastLines' to each @last 
  # directive found.  NOTE: the @last directives must be the very last
  # lines in 'out'.
  def completeLastDirectives(self,out,lastLines):
  
    tag = "@last"
    foundAtLastYet = 0
    outRange = range(-1,-len(out),-1)
    j = -1
    for k in outRange:
      # skip trailing whitespace lines
      if (not foundAtLastYet) and (len(out[k].strip()) == 0): continue
      # quit if something other than @last directive
      i = 0
      if not match(out[k],i,tag): break;
      foundAtLastYet = 1
      # quit if no trailing lines to apply
      if j < -len(lastLines): break
      # make the new @last directive
      #18-SEP-2002 DTHEIN: remove trailing newlines because they are inserted later
      # 21-SEP-2002 DTHEIN: no trailing whitespace on empty @last directive
      trailingLine = " " + lastLines[j]
      out[k] = tag + trailingLine.rstrip() ; j -= 1
  #@-node:completeLastDirectives
  #@+node:createNthChild
  #@+at 
  #@nonl
  # Sections appear in the derived file in reference order, not tree order.  
  # Therefore, when we insert the nth child of the parent there is no 
  # guarantee that the previous n-1 children have already been inserted. And 
  # it won't work just to insert the nth child as the last child if there 
  # aren't n-1 previous siblings.  For example, if we insert the third child 
  # followed by the second child followed by the first child the second and 
  # third children will be out of order.
  # 
  # To ensure that nodes are placed in the correct location we create 
  # "dummy" children as needed as placeholders.  In the example above, we 
  # would insert two dummy children when inserting the third child.  When 
  # inserting the other two children we replace the previously inserted 
  # dummy child with the actual children.
  # 
  # vnode child indices are zero-based.  Here we use 1-based indices.
  # 
  # With the "mirroring" scheme it is a structure error if we ever have to 
  # create dummy vnodes.  Such structure errors cause a second pass to be 
  # made, with an empty root.  This second pass will generate other 
  # structure errors, which are ignored.
  #@-at
  #@@c
  def createNthChild(self,n,parent,headline):
    
    """Create the nth child of the parent."""
  
    at = self
    assert(n > 0)
    
    if at.importing:
      return at.createImportedNode(at.root,at.c,headline)
  
    # Create any needed dummy children.
    dummies = n - parent.numberOfChildren() - 1
    if dummies > 0:
      if 0: # CVS produces to many errors for this to be useful.
        es("dummy created")
      self.errors += 1
    while dummies > 0:
      dummies -= 1
      dummy = parent.insertAsLastChild(leoNodes.tnode())
      # The user should never see this headline.
      dummy.initHeadString("Dummy")
  
    if n <= parent.numberOfChildren():
      #@    << check the headlines >>
      #@+node:<< check the headlines >>
      # 1/24/03: A kludgy fix to the problem of headlines containing comment delims.
      
      result = parent.nthChild(n-1)
      resulthead = result.headString()
      
      if headline.strip() != resulthead.strip():
        start = self.startSentinelComment
        end = self.endSentinelComment
        if end and len(end) > 0:
          # 1/25/03: The kludgy fix.
          # Compare the headlines without the delims.
          h1 =   headline.replace(start,"").replace(end,"")
          h2 = resulthead.replace(start,"").replace(end,"")
          if h1.strip() == h2.strip():
            # 1/25/03: Another kludge: use the headline from the outline, not the derived file.
            headline = resulthead
          else:
            self.errors += 1
        else:
          self.errors += 1
      #@-node:<< check the headlines >>
      #@nl
    else:
      # This is using a dummy; we should already have bumped errors.
      result = parent.insertAsLastChild(leoNodes.tnode())
    result.initHeadString(headline)
    
    result.setVisited() # Suppress all other errors for this node.
    result.t.setVisited() # Suppress warnings about unvisited nodes.
    return result
  #@nonl
  #@-node:createNthChild
  #@+node:handleLinesFollowingSentinel
  def handleLinesFollowingSentinel (self,lines,sentinel,comments = true):
    
    """convert lines following a sentinel to a single line"""
    
    m = " following" + sentinel + " sentinel"
    start = self.startSentinelComment
    end   = self.endSentinelComment
    
    if len(lines) == 1: # The expected case.
      s = lines[0]
    elif len(lines) == 5:
      self.readError("potential cvs conflict" + m)
      s = lines[1]
      es("using " + s)
    else:
      self.readError("unexpected lines" + m)
      es(len(lines), " lines" + m)
      s = "bad " + sentinel
      if comments: s = start + ' ' + s
  
    if comments:
      #@    << remove the comment delims from s >>
      #@+node:<< remove the comment delims from s >>
      # Remove the starting comment and the blank.
      # 5/1/03: The starting comment now looks like a sentinel, to warn users from changing it.
      comment = start + '@ '
      if match(s,0,comment):
        s = s[len(comment):]
      else:
        self.readError("expecting comment" + m)
      
      # Remove the trailing comment.
      if len(end) == 0:
        s = string.strip(s[:-1])
      else:
        k = s.rfind(end)
        s = string.strip(s[:k]) # works even if k == -1
      #@nonl
      #@-node:<< remove the comment delims from s >>
      #@nl
      
    # Undo the cweb hack: undouble @ signs if the opening comment delim ends in '@'.
    if start[-1:] == '@':
      s = s.replace('@@','@')
  
    return s
  #@nonl
  #@-node:handleLinesFollowingSentinel
  #@+node:readLine
  def readLine (self,file):
    """Reads one line from file using the present encoding"""
    
    s = readlineForceUnixNewline(file)
    u = toUnicode(s,self.encoding)
    return u
  
  #@-node:readLine
  #@+node:readLinesToNextSentinel
  # We expect only a single line, and more may exist if cvs detects a conflict.
  # We accept the first line even if it looks like a sentinel.
  # 5/1/03: The starting comment now looks like a sentinel, to warn users from changing it.
  
  def readLinesToNextSentinel (self,file):
    
    """  read lines following multiline sentinels"""
    
    lines = []
    start = self.startSentinelComment + '@ '
    nextLine = self.readLine(file)
    while nextLine and len(nextLine) > 0:
      if len(lines) == 0:
        lines.append(nextLine)
        nextLine = self.readLine(file)
      else:
        # 5/1/03: looser test then calling sentinelKind.
        s = nextLine ; i = skip_ws(s,0)
        if match(s,i,start):
          lines.append(nextLine)
          nextLine = self.readLine(file)
        else: break
  
    return nextLine,lines
  #@nonl
  #@-node:readLinesToNextSentinel
  #@+node:scanDoc
  # Scans the doc part and appends the text out.
  # s,i point to the present line on entry.
  
  def scanDoc(self,file,s,i,out,kind):
  
    endKind = choose(kind ==startDoc,endDoc,endAt)
    single = len(self.endSentinelComment) == 0
    #@  << Skip the opening sentinel >>
    #@+node:<< Skip the opening sentinel >>
    assert(match(s,i,choose(kind == startDoc, "+doc", "+at")))
    
    out.append(choose(kind == startDoc, "@doc", "@"))
    s = self.readLine(file)
    #@-node:<< Skip the opening sentinel >>
    #@nl
    #@  << Skip an opening block delim >>
    #@+node:<< Skip an opening block delim >>
    if not single:
      j = skip_ws(s,0)
      if match(s,j,self.startSentinelComment):
        s = self.readLine(file)
    #@nonl
    #@-node:<< Skip an opening block delim >>
    #@nl
    nextLine = None ; kind = noSentinel
    while len(s) > 0:
      #@    << set kind, nextLine >>
      #@+node:<< set kind, nextLine >>
      #@+at 
      #@nonl
      # For non-sentinel lines we look ahead to see whether the next 
      # line is a sentinel.
      #@-at
      #@@c
      
      assert(nextLine==None)
      
      kind = self.sentinelKind(s)
      
      if kind == noSentinel:
        j = skip_ws(s,0)
        blankLine = s[j] == '\n'
        nextLine = self.readLine(file)
        nextKind = self.sentinelKind(nextLine)
        if blankLine and nextKind == endKind:
          kind = endKind # stop the scan now
      #@-node:<< set kind, nextLine >>
      #@nl
      if kind == endKind: break
      #@    << Skip the leading stuff >>
      #@+node:<< Skip the leading stuff >>
      # Point i to the start of the real line.
      
      if single: # Skip the opening comment delim and a blank.
        i = skip_ws(s,0)
        if match(s,i,self.startSentinelComment):
          i += len(self.startSentinelComment)
          if match(s,i," "): i += 1
      else:
        i = self.skipIndent(s,0, self.indent)
      #@-node:<< Skip the leading stuff >>
      #@nl
      #@    << Append s to out >>
      #@+node:<< Append s to out >>
      # Append the line with a newline if it is real
      
      line = s[i:-1] # remove newline for rstrip.
      
      if line == line.rstrip():
        # no trailing whitespace: the newline is real.
        out.append(line + '\n')
      else:
        # trailing whitespace: the newline is not real.
        out.append(line)
      #@-node:<< Append s to out >>
      #@nl
      if nextLine:
        s = nextLine ; nextLine = None
      else: s = self.readLine(file)
    if kind != endKind:
      self.readError("Missing " + self.sentinelName(endKind) + " sentinel")
    #@  << Remove a closing block delim from out >>
    #@+node:<< Remove a closing block delim from out >>
    # This code will typically only be executed for HTML files.
    
    if not single:
    
      delim = self.endSentinelComment
      n = len(delim)
      
      # Remove delim and possible a leading newline.
      s = string.join(out,"")
      s = s.rstrip()
      if s[-n:] == delim:
        s = s[:-n]
      if s[-1] == '\n':
        s = s[:-1]
        
      # Rewrite out in place.
      del out[:]
      out.append(s)
    #@-node:<< Remove a closing block delim from out >>
    #@nl
  #@nonl
  #@-node:scanDoc
  #@+node:scanText
  def scanText (self,file,v,out,endSentinelKind,nextLine=None):
    
    """Scan a 3.x derived file recursively."""
  
    lastLines = [] # The lines after @-leo
    lineIndent = 0 ; linep = 0 # Changed only for sentinels.
    while 1:
      #@    << put the next line into s >>
      #@+node:<< put the next line into s >>
      if nextLine:
        s = nextLine ; nextLine = None
      else:
        s = self.readLine(file)
        if len(s) == 0: break
      #@nonl
      #@-node:<< put the next line into s >>
      #@nl
      #@    << set kind, nextKind >>
      #@+node:<< set kind, nextKind >>
      #@+at 
      #@nonl
      # For non-sentinel lines we look ahead to see whether the next 
      # line is a sentinel.  If so, the newline that ends a non-sentinel 
      # line belongs to the next sentinel.
      #@-at
      #@@c
      
      assert(nextLine==None)
      
      kind = self.sentinelKind(s)
      
      if kind == noSentinel:
        nextLine = self.readLine(file)
        nextKind = self.sentinelKind(nextLine)
      else:
        nextLine = nextKind = None
      
      # nextLine != None only if we have a non-sentinel line.
      # Therefore, nextLine == None whenever scanText returns.
      #@nonl
      #@-node:<< set kind, nextKind >>
      #@nl
      if kind != noSentinel:
        #@      << set lineIndent, linep and leading_ws >>
        #@+node:<< Set lineIndent, linep and leading_ws >>
        #@+at 
        #@nonl
        # lineIndent is the total indentation on a sentinel line.  The 
        # first "self.indent" portion of that must be removed when 
        # recreating text.  leading_ws is the remainder of the leading 
        # whitespace.  linep points to the first "real" character of a 
        # line, the character following the "indent" whitespace.
        #@-at
        #@@c
        
        # Point linep past the first self.indent whitespace characters.
        if self.raw: # 10/15/02
          linep =0
        else:
          linep = self.skipIndent(s,0,self.indent)
        
        # Set lineIndent to the total indentation on the line.
        lineIndent = 0 ; i = 0
        while i < len(s):
          if s[i] == '\t': lineIndent += (abs(self.tab_width) - (lineIndent % abs(self.tab_width)))
          elif s[i] == ' ': lineIndent += 1
          else: break
          i += 1
        # trace("lineIndent:" +`lineIndent` + ", " + `s`)
        
        # Set leading_ws to the additional indentation on the line.
        leading_ws = s[linep:i]
        #@nonl
        #@-node:<< Set lineIndent, linep and leading_ws >>
        #@nl
        i = self.skipSentinelStart(s,0)
      #@    << handle the line in s >>
      #@+node:<< handle the line in s >>
      if kind == noSentinel:
        #@  << append non-sentinel line >>
        #@+node:<< append non-sentinel line >>
        # We don't output the trailing newline if the next line is a sentinel.
        if self.raw: # 10/15/02
          i = 0
        else:
          i = self.skipIndent(s,0,self.indent)
        
        assert(nextLine != None)
        
        if nextKind == noSentinel:
          line = s[i:]
          out.append(line)
        else:
          line = s[i:-1] # don't output the newline
          out.append(line)
        #@-node:<< append non-sentinel line >>
        #@nl
      #@<< handle common sentinels >>
      #@+node:<< handle common sentinels >>
      elif kind in (endAt, endBody,endDoc,endLeo,endNode,endOthers):
          #@    << handle an ending sentinel >>
          #@+node:<< handle an ending sentinel >>
          # trace("end sentinel:", self.sentinelName(kind))
          
          if kind == endSentinelKind:
            if kind == endLeo:
              # Ignore everything after @-leo.
              # Such lines were presumably written by @last.
              while 1:
                s = self.readLine(file)
                if len(s) == 0: break
                lastLines.append(s) # Capture all trailing lines, even if empty.
            elif kind == endBody:
              self.raw = false
            # nextLine != None only if we have a non-sentinel line.
            # Therefore, nextLine == None whenever scanText returns.
            assert(nextLine==None)
            return lastLines # End the call to scanText.
          else:
            # Tell of the structure error.
            name = self.sentinelName(kind)
            expect = self.sentinelName(endSentinelKind)
            self.readError("Ignoring " + name + " sentinel.  Expecting " + expect)
          #@nonl
          #@-node:<< handle an ending sentinel >>
          #@nl
      elif kind == startBody:
        #@  << scan @+body >>
        #@+node:<< scan @+body >>
        assert(match(s,i,"+body"))
        
        child_out = [] ; child = v # Do not change out or v!
        oldIndent = self.indent ; self.indent = lineIndent
        self.scanText(file,child,child_out,endBody)
        
        # Set the body, removing cursed newlines.
        # This must be done here, not in the @+node logic.
        body = string.join(child_out, "")
        body = body.replace('\r', '')
        body = toUnicode(body,app.tkEncoding) # 9/28/03
        
        if self.importing:
          child.t.bodyString = body
        else:
          child.t.tempBodyString = body
        
        self.indent = oldIndent
        #@nonl
        #@-node:<< scan @+body >>
        #@nl
      elif kind == startNode:
        #@  << scan @+node >>
        #@+node:<< scan @+node >>
        assert(match(s,i,"+node:"))
        i += 6
        
        childIndex = 0 ; cloneIndex = 0
        #@<< Set childIndex >>
        #@+node:<< Set childIndex >>
        i = skip_ws(s,i) ; j = i
        while i < len(s) and s[i] in string.digits:
          i += 1
        
        if j == i:
          self.readError("Implicit child index in @+node")
          childIndex = 0
        else:
          childIndex = int(s[j:i])
        
        if match(s,i,':'):
          i += 1 # Skip the ":".
        else:
          self.readError("Bad child index in @+node")
        #@nonl
        #@-node:<< Set childIndex >>
        #@nl
        #@<< Set cloneIndex >>
        #@+node:<< Set cloneIndex >>
        while i < len(s) and s[i] != ':' and not is_nl(s,i):
          if match(s,i,"C="):
            # set cloneIndex from the C=nnn, field
            i += 2 ; j = i
            while i < len(s) and s[i] in string.digits:
              i += 1
            if j < i:
              cloneIndex = int(s[j:i])
          else: i += 1 # Ignore unknown status bits.
        
        if match(s,i,":"):
          i += 1
        else:
          self.readError("Bad attribute field in @+node")
        #@nonl
        #@-node:<< Set cloneIndex >>
        #@nl
        headline = ""
        #@<< Set headline and ref >>
        #@+node:<< Set headline and ref >>
        # Set headline to the rest of the line.
        # 6/22/03: don't strip leading whitespace.
        if len(self.endSentinelComment) == 0:
          headline = s[i:-1].rstrip()
        else:
          # 10/24/02: search from the right, not the left.
          k = s.rfind(self.endSentinelComment,i)
          headline = s[i:k].rstrip() # works if k == -1
          
        # 10/23/02: The cweb hack: undouble @ signs if the opening comment delim ends in '@'.
        if self.startSentinelComment[-1:] == '@':
          headline = headline.replace('@@','@')
        
        # Set reference if it exists.
        i = skip_ws(s,i)
        
        if 0: # no longer used
          if match(s,i,"<<"):
            k = s.find(">>",i)
            if k != -1: ref = s[i:k+2]
        #@nonl
        #@-node:<< Set headline and ref >>
        #@nl
        
        # print childIndex,headline
        
        if childIndex == 0: # The root node.
          if not at.importing:
            #@    << Check the filename in the sentinel >>
            #@+node:<< Check the filename in the sentinel >>
            h = headline.strip()
            
            if h[:5] == "@file":
              i,junk,junk = scanAtFileOptions(h)
              fileName = string.strip(h[i:])
              if fileName != self.targetFileName:
                self.readError("File name in @node sentinel does not match file's name")
            elif h[:8] == "@rawfile":
              fileName = string.strip(h[8:])
              if fileName != self.targetFileName:
                self.readError("File name in @node sentinel does not match file's name")
            else:
              self.readError("Missing @file in root @node sentinel")
            #@-node:<< Check the filename in the sentinel >>
            #@nl
          # Put the text of the root node in the current node.
          self.scanText(file,v,out,endNode)
          v.t.setCloneIndex(cloneIndex)
          # if cloneIndex > 0: trace("clone index:" + `cloneIndex` + ", " + `v`)
        else:
          # NB: this call to createNthChild is the bottleneck!
          child = self.createNthChild(childIndex,v,headline)
          child.t.setCloneIndex(cloneIndex)
          # if cloneIndex > 0: trace("clone index:" + `cloneIndex` + ", " + `child`)
          self.scanText(file,child,out,endNode)
        
        #@<< look for sentinels that may follow a reference >>
        #@+node:<< look for sentinels that may follow a reference >>
        s = self.readLine(file)
        kind = self.sentinelKind(s)
        
        if len(s) > 1 and kind == startVerbatimAfterRef:
          s = self.readLine(file)
          # trace("verbatim:"+`s`)
          out.append(s)
        elif len(s) > 1 and self.sentinelKind(s) == noSentinel:
          out.append(s)
        else:
          nextLine = s # Handle the sentinel or blank line later.
        #@-node:<< look for sentinels that may follow a reference >>
        #@nl
        #@nonl
        #@-node:<< scan @+node >>
        #@nl
      elif kind == startRef:
        #@  << scan old ref >>
        #@+node:<< scan old ref >> (3.0)
        #@+at 
        #@nonl
        # The sentinel contains an @ followed by a section name in 
        # angle brackets.  This code is different from the code for 
        # the @@ sentinel: the expansion of the reference does not 
        # include a trailing newline.
        #@-at
        #@@c
        
        assert(match(s,i,"<<"))
        
        if len(self.endSentinelComment) == 0:
          line = s[i:-1] # No trailing newline
        else:
          k = s.find(self.endSentinelComment,i)
          line = s[i:k] # No trailing newline, whatever k is.
            
        # 10/30/02: undo cweb hack here
        start = self.startSentinelComment
        if start and len(start) > 0 and start[-1] == '@':
          line = line.replace('@@','@')
        
        out.append(line)
        #@nonl
        #@-node:<< scan old ref >> (3.0)
        #@nl
      elif kind == startAt:
        #@  << scan @+at >>
        #@+node:<< scan @+at >>
        assert(match(s,i,"+at"))
        self.scanDoc(file,s,i,out,kind)
        #@nonl
        #@-node:<< scan @+at >>
        #@nl
      elif kind == startDoc:
        #@  << scan @+doc >>
        #@+node:<< scan @+doc >>
        assert(match(s,i,"+doc"))
        self.scanDoc(file,s,i,out,kind)
        #@nonl
        #@-node:<< scan @+doc >>
        #@nl
      elif kind == startOthers:
        #@  << scan @+others >>
        #@+node:<< scan @+others >>
        assert(match(s,i,"+others"))
        
        # Make sure that the generated at-others is properly indented.
        out.append(leading_ws + "@others")
        
        self.scanText(file,v,out,endOthers)
        #@nonl
        #@-node:<< scan @+others >>
        #@nl
      #@nonl
      #@-node:<< handle common sentinels >>
      #@nl
      #@<< handle rare sentinels >>
      #@+node:<< handle rare sentinels >>
      elif kind == startComment:
        #@  << scan @comment >>
        #@+node:<< scan @comment >>
        assert(match(s,i,"comment"))
        
        # We need do nothing more to ignore the comment line!
        #@-node:<< scan @comment >>
        #@nl
      elif kind == startDelims:
        #@  << scan @delims >>
        #@+node:<< scan @delims >>
        assert(match(s,i-1,"@delims"));
        
        # Skip the keyword and whitespace.
        i0 = i-1
        i = skip_ws(s,i-1+7)
          
        # Get the first delim.
        j = i
        while i < len(s) and not is_ws(s[i]) and not is_nl(s,i):
          i += 1
        
        if j < i:
          self.startSentinelComment = s[j:i]
          # print "delim1:", self.startSentinelComment
        
          # Get the optional second delim.
          j = i = skip_ws(s,i)
          while i < len(s) and not is_ws(s[i]) and not is_nl(s,i):
            i += 1
          end = choose(j<i,s[j:i],"")
          i2 = skip_ws(s,i)
          if end == self.endSentinelComment and (i2 >= len(s) or is_nl(s,i2)):
            self.endSentinelComment = "" # Not really two params.
            line = s[i0:j]
            line = line.rstrip()
            out.append(line+'\n')
          else:
            self.endSentinelComment = end
            # print "delim2:",end
            line = s[i0:i]
            line = line.rstrip()
            out.append(line+'\n')
        else:
          self.readError("Bad @delims")
          # Append the bad @delims line to the body text.
          out.append("@delims")
        #@nonl
        #@-node:<< scan @delims >>
        #@nl
      elif kind == startDirective:
        #@  << scan @@ >>
        #@+node:<< scan @@ >>
        # The first '@' has already been eaten.
        assert(match(s,i,"@"))
        
        if match_word(s,i,"@raw"):
          self.raw = true
        elif match_word(s,i,"@end_raw"):
          self.raw = false
        
        e = self.endSentinelComment
        s2 = s[i:]
        if len(e) > 0:
          k = s.rfind(e,i)
          if k != -1:
            s2 = s[i:k] + '\n'
          
        start = self.startSentinelComment
        if start and len(start) > 0 and start[-1] == '@':
          s2 = s2.replace('@@','@')
        out.append(s2)
        # trace(`s2`)
        #@nonl
        #@-node:<< scan @@ >>
        #@nl
      elif kind == startLeo:
        #@  << scan @+leo >>
        #@+node:<< scan @+leo >>
        assert(match(s,i,"+leo"))
        self.readError("Ignoring unexpected @+leo sentinel")
        #@nonl
        #@-node:<< scan @+leo >>
        #@nl
      elif kind == startVerbatim:
        #@  << scan @verbatim >>
        #@+node:<< scan @verbatim >>
        assert(match(s,i,"verbatim"))
        
        # Skip the sentinel.
        s = self.readLine(file) 
        
        # Append the next line to the text.
        i = self.skipIndent(s,0,self.indent)
        out.append(s[i:])
        #@-node:<< scan @verbatim >>
        #@nl
      #@nonl
      #@-node:<< handle rare sentinels >>
      #@nl
      else:
        #@  << warn about unknown sentinel >>
        #@+node:<< warn about unknown sentinel >>
        j = i
        i = skip_line(s,i)
        line = s[j:i]
        self.readError("Unknown sentinel: " + line)
        #@nonl
        #@-node:<< warn about unknown sentinel >>
        #@nl
      #@nonl
      #@-node:<< handle the line in s >>
      #@nl
    #@  << handle unexpected end of text >>
    #@+node:<< handle unexpected end of text >>
    # Issue the error.
    name = self.sentinelName(endSentinelKind)
    self.readError("Unexpected end of file. Expecting " + name + "sentinel" )
    #@-node:<< handle unexpected end of text >>
    #@nl
    assert(len(s)==0 and nextLine==None) # We get here only if readline fails.
    return lastLines # We get here only if there are problems.
  #@nonl
  #@-node:scanText
  #@+node:nodeSentinelText
  # 4/5/03: config.write_clone_indices no longer used.
  
  def nodeSentinelText(self,v):
    
    if v == self.root or not v.parent():
      index = 0
    else:
      index = v.childIndex() + 1
  
    h = v.headString()
    #@  << remove comment delims from h if necessary >>
    #@+node:<< remove comment delims from h if necessary >>
    #@+at 
    #@nonl
    # Bug fix 1/24/03:
    # 
    # If the present @language/@comment settings do not specify a 
    # single-line comment we remove all block comment delims from h.  This 
    # prevents headline text from interfering with the parsing of node 
    # sentinels.
    #@-at
    #@@c
    
    start = self.startSentinelComment
    end = self.endSentinelComment
    
    if end and len(end) > 0:
      h = h.replace(start,"")
      h = h.replace(end,"")
    #@nonl
    #@-node:<< remove comment delims from h if necessary >>
    #@nl
  
    return str(index) + '::' + h
  #@nonl
  #@-node:nodeSentinelText
  #@+node:putCloseNodeSentinel
  def putCloseNodeSentinel(self,v):
  
    s = self.nodeSentinelText(v)
    self.putSentinel("@-node:" + s)
  #@nonl
  #@-node:putCloseNodeSentinel
  #@+node:putCloseSentinels
  # root is an ancestor of v, or root == v.
  
  def putCloseSentinels(self,root,v):
    
    """call putCloseSentinel for v up to, but not including, root."""
  
    self.putCloseNodeSentinel(v)
    while 1:
      v = v.parent()
      assert(v) # root must be an ancestor of v.
      if  v == root: break
      self.putCloseNodeSentinel(v)
  #@nonl
  #@-node:putCloseSentinels
  #@+node:putOpenLeoSentinel
  # This method is the same as putSentinel except we don't put an opening newline and leading whitespace.
  
  def putOpenLeoSentinel(self,s):
    
    """Put a +leo sentinel containing s."""
    
    if not self.sentinels:
      return # Handle @nosentinelsfile.
  
    self.os(self.startSentinelComment)
    self.os(s)
    encoding = self.encoding.lower()
    if encoding != "utf-8":
      self.os("-encoding=")
      self.os(encoding)
      self.os(".")
    self.os(self.endSentinelComment)
    if self.suppress_newlines: # 9/27/02
      self.newline_pending = true # Schedule a newline.
    else:
      self.onl() # End of sentinel.
  #@-node:putOpenLeoSentinel
  #@+node:putOpenNodeSentinel
  def putOpenNodeSentinel(self,v):
  
    """Put an open node sentinel for node v."""
  
    if v.isAtFileNode() and v != self.root:
      self.writeError("@file not valid in: " + v.headString())
      return
    
    s = self.nodeSentinelText(v)
    self.putSentinel("@+node:" + s)
  #@nonl
  #@-node:putOpenNodeSentinel
  #@+node:putOpenSentinels
  # root is an ancestor of v, or root == v.
  
  def putOpenSentinels(self,root,v):
  
    """Call putOpenNodeSentinel on all the descendents of root which are the ancestors of v."""
  
    last = root
    while last != v:
      # Set node to v or the ancestor of v that is a child of last.
      node = v
      while node and node.parent() != last:
        node = node.parent()
      assert(node)
      self.putOpenNodeSentinel(node)
      last = node
  #@nonl
  #@-node:putOpenSentinels
  #@+node:putSentinel (applies cweb hack)
  #@+at 
  #@nonl
  # All sentinels are eventually output by this method.
  # 
  # Sentinels include both the preceding and following newlines. This rule 
  # greatly simplies the code and has several important benefits:
  # 
  # 1. Callers never have to generate newlines before or after sentinels.  
  # Similarly, routines that expand code and doc parts never have to add 
  # "extra" newlines.
  # 2. There is no need for a "no-newline" directive.  If text follows a 
  # section reference, it will appear just after the newline that ends 
  # sentinel at the end of the expansion of the reference.  If no 
  # significant text follows a reference, there will be two newlines 
  # following the ending sentinel.
  # 
  # The only exception is that no newline is required before the opening 
  # "leo" sentinel. The putLeoSentinel and isLeoSentinel routines handle 
  # this minor exception.
  #@-at
  #@@c
  def putSentinel(self,s):
    
    """Put a sentinel containing s."""
    
    if not self.sentinels:
      return # Handle @nosentinelsfile.
  
    self.newline_pending = false # discard any pending newline.
    self.onl() ; self.putIndent(self.indent) # Start of sentinel.
    self.os(self.startSentinelComment)
  
    # 11/1/02: The cweb hack: if the opening comment delim ends in '@',
    # double all '@' signs except the first, which is "doubled" by the
    # trailing '@' in the opening comment delimiter.
    start = self.startSentinelComment
    if start and len(start) > 0 and start[-1] == '@':
      assert(s and len(s)>0 and s[0]=='@')
      s = s.replace('@','@@')[1:]
  
    self.os(s)
    self.os(self.endSentinelComment)
    if self.suppress_newlines:
      self.newline_pending = true # Schedule a newline.
    else:
      self.onl() # End of sentinel.
  #@nonl
  #@-node:putSentinel (applies cweb hack)
  #@+node:sentinelKind
  def sentinelKind(self,s):
  
    """This method tells what kind of sentinel appears in line s.
    
    Typically s will be an empty line before the actual sentinel,
    but it is also valid for s to be an actual sentinel line.
    
    Returns (kind, s, emptyFlag), where emptyFlag is true if
    kind == noSentinel and s was an empty line on entry."""
  
    i = skip_ws(s,0)
    if match(s,i,self.startSentinelComment):
      i += len(self.startSentinelComment)
    else:
      return noSentinel
  
    # 10/30/02: locally undo cweb hack here
    start = self.startSentinelComment
    if start and len(start) > 0 and start[-1] == '@':
      s = s[:i] + string.replace(s[i:],'@@','@')
  
    # Do not skip whitespace here!
    if match(s,i,"@<<"): return startRef
    if match(s,i,"@@"):   return startDirective
    if not match(s,i,'@'): return noSentinel
    j = i # start of lookup
    i += 1 # skip the at sign.
    if match(s,i,'+') or match(s,i,'-'):
      i += 1
    i = skip_c_id(s,i)
    key = s[j:i]
    if len(key) > 0 and sentinelDict.has_key(key):
      # trace("found:",key)
      return sentinelDict[key]
    else:
      # trace("not found:",key)
      return noSentinel
  #@nonl
  #@-node:sentinelKind
  #@+node:sentinelName
  # Returns the name of the sentinel for warnings.
  
  def sentinelName(self, kind):
  
    sentinelNameDict = {
      noSentinel:  "<no sentinel>",
      startAt:     "@+at",     endAt:     "@-at",
      startBody:   "@+body",   endBody:   "@-body", # 3.x only.
      startDoc:    "@+doc",    endDoc:    "@-doc",
      startLeo:    "@+leo",    endLeo:    "@-leo",
      startNode:   "@+node",   endNode:   "@-node",
      startOthers: "@+others", endOthers: "@-others",
      startAfterRef:  "@afterref", # 4.x
      startComment:   "@comment",
      startDelims:    "@delims",
      startDirective: "@@",
      startNl:        "@nl",   # 4.x
      startNonl:      "@nonl", # 4.x
      startRef:       "@<<",
      startVerbatim:  "@verbatim",
      startVerbatimAfterRef: "@verbatimAfterRef" } # 3.x only.
  
    return sentinelNameDict.get(kind,"<unknown sentinel!>")
  #@nonl
  #@-node:sentinelName
  #@+node:skipSentinelStart
  def skipSentinelStart(self,s,i):
  
    start = self.startSentinelComment
    assert(start and len(start)>0)
  
    if is_nl(s,i): i = skip_nl(s,i)
    i = skip_ws(s,i)
    assert(match(s,i,start))
    i += len(start)
    # 7/8/02: Support for REM hack
    i = skip_ws(s,i)
    assert(i < len(s) and s[i] == '@')
    return i + 1
  #@-node:skipSentinelStart
  #@+node:directiveKind
  # Returns the kind of at-directive or noDirective.
  
  def directiveKind(self,s,i):
  
    n = len(s)
    if i >= n or s[i] != '@':
      return noDirective
  
    table = (
      ("@c",cDirective),
      ("@code",codeDirective),
      ("@doc",docDirective),
      ("@end_raw",endRawDirective),
      ("@others",othersDirective),
      ("@raw",rawDirective))
  
    # This code rarely gets executed, so simple code suffices.
    if i+1 >= n or match(s,i,"@ ") or match(s,i,"@\t") or match(s,i,"@\n"):
      # 10/25/02: @space is not recognized in cweb mode.
      # 11/15/02: Noweb doc parts are _never_ scanned in cweb mode.
      return choose(self.language=="cweb",
        noDirective,atDirective)
  
    # 10/28/02: @c and @(nonalpha) are not recognized in cweb mode.
    # We treat @(nonalpha) separately because @ is in the colorizer table.
    if self.language=="cweb" and (
      match_word(s,i,"@c") or
      i+1>= n or s[i+1] not in string.ascii_letters):
      return noDirective
  
    for name,directive in table:
      if match_word(s,i,name):
        return directive
    # 10/14/02: return miscDirective only for real directives.
    for name in leoColor.leoKeywords:
      if match_word(s,i,name):
        return miscDirective
  
    return noDirective
  #@nonl
  #@-node:directiveKind
  #@+node:error
  def error(self,message):
  
    es_error(message)
    self.errors += 1
  #@-node:error
  #@+node:readError
  def readError(self,message):
  
    # This is useful now that we don't print the actual messages.
    if self.errors == 0:
      es_error("----- error reading @file " + self.targetFileName)
      self.error(message) # 9/10/02: we must increment self.errors!
      
    print message
  
    if 0: # CVS conflicts create too many messages.
      self.error(message)
    
    self.root.setOrphan()
    self.root.setDirty()
  #@nonl
  #@-node:readError
  #@+node:scanAllDirectives
  #@+at 
  #@nonl
  # Once a directive is seen, no other related directives in nodes further 
  # up the tree have any effect.  For example, if an @color directive is 
  # seen in node v, no @color or @nocolor directives are examined in any 
  # ancestor of v.
  # 
  # This code is similar to Commands::scanAllDirectives, but it has been 
  # modified for use by the atFile class.
  #@-at
  #@@c
  
  def scanAllDirectives(self,v):
    
    """Scan vnode v and v's ancestors looking for directives,
    setting corresponding atFile ivars.
    """
  
    c = self.c
    #@  << Set ivars >>
    #@+node:<< Set ivars >>
    self.page_width = self.c.page_width
    self.tab_width  = self.c.tab_width
    
    self.default_directory = None # 8/2: will be set later.
    
    delim1, delim2, delim3 = set_delims_from_language(c.target_language)
    self.language = c.target_language
    
    self.encoding = app.config.default_derived_file_encoding
    self.output_newline = getOutputNewline() # 4/24/03: initialize from config settings.
    #@nonl
    #@-node:<< Set ivars >>
    #@nl
    #@  << Set path from @file node >>
    #@+node:<< Set path from @file node >> in scanDirectory in leoGlobals.py
    # An absolute path in an @file node over-rides everything else.
    # A relative path gets appended to the relative path by the open logic.
    
    # Bug fix: 10/16/02
    if v.isAtFileNode():
      name = v.atFileNodeName()
    elif v.isAtRawFileNode():
      name = v.atRawFileNodeName()
    elif v.isAtNoSentinelsFileNode():
      name = v.atNoSentinelsFileNodeName()
    else:
      name = ""
    
    dir = choose(name,os_path_dirname(name),None)
    
    if dir and len(dir) > 0 and os_path_isabs(dir):
      if os_path_exists(dir):
        self.default_directory = dir
      else: # 9/25/02
        self.default_directory = makeAllNonExistentDirectories(dir)
        if not self.default_directory:
          self.error("Directory \"" + dir + "\" does not exist")
          
    #@-node:<< Set path from @file node >> in scanDirectory in leoGlobals.py
    #@nl
    old = {}
    while v:
      s = v.t.bodyString
      dict = get_directives_dict(s)
      #@    << Test for @path >>
      #@+node:<< Test for @path >>
      # We set the current director to a path so future writes will go to that directory.
      
      if not self.default_directory and not old.has_key("path") and dict.has_key("path"):
      
        k = dict["path"]
        #@  << compute relative path from s[k:] >>
        #@+node:<< compute relative path from s[k:] >>
        j = i = k + len("@path")
        i = skip_to_end_of_line(s,i)
        path = string.strip(s[j:i])
        
        # Remove leading and trailing delims if they exist.
        if len(path) > 2 and (
          (path[0]=='<' and path[-1] == '>') or
          (path[0]=='"' and path[-1] == '"') ):
          path = path[1:-1]
        path = path.strip()
        
        if 0: # 11/14/02: we want a _relative_ path, not an absolute path.
          path = os_path_join(app.loadDir,path)
        #@nonl
        #@-node:<< compute relative path from s[k:] >>
        #@nl
        if path and len(path) > 0:
          base = getBaseDirectory() # returns "" on error.
          path = os_path_join(base,path)
          if os_path_isabs(path):
            #@      << handle absolute path >>
            #@+node:<< handle absolute path >>
            # path is an absolute path.
            
            if os_path_exists(path):
              self.default_directory = path
            else: # 9/25/02
              self.default_directory = makeAllNonExistentDirectories(path)
              if not self.default_directory:
                self.error("invalid @path: " + path)
            #@-node:<< handle absolute path >>
            #@nl
          else:
            self.error("ignoring bad @path: " + path)
        else:
          self.error("ignoring empty @path")
      #@nonl
      #@-node:<< Test for @path >>
      #@nl
      #@    << Test for @encoding >>
      #@+node:<< Test for @encoding >>
      if not old.has_key("encoding") and dict.has_key("encoding"):
        
        e = scanAtEncodingDirective(s,dict)
        if e:
          self.encoding = e
      #@nonl
      #@-node:<< Test for @encoding >>
      #@nl
      #@    << Test for @comment and @language >>
      #@+node:<< Test for @comment and @language >>
      # 10/17/02: @language and @comment may coexist in @file trees.
      # For this to be effective the @comment directive should follow the @language directive.
      
      if not old.has_key("comment") and dict.has_key("comment"):
        k = dict["comment"]
        # 11/14/02: Similar to fix below.
        delim1, delim2, delim3 = set_delims_from_string(s[k:])
      
      # Reversion fix: 12/06/02: We must use elif here, not if.
      elif not old.has_key("language") and dict.has_key("language"):
        k = dict["language"]
        # 11/14/02: Fix bug reported by J.M.Gilligan.
        self.language,delim1,delim2,delim3 = set_language(s,k)
      #@nonl
      #@-node:<< Test for @comment and @language >>
      #@nl
      #@    << Test for @header and @noheader >>
      #@+node:<< Test for @header and @noheader >>
      # EKR: 10/10/02: perform the sames checks done by tangle.scanAllDirectives.
      if dict.has_key("header") and dict.has_key("noheader"):
        es("conflicting @header and @noheader directives")
      #@nonl
      #@-node:<< Test for @header and @noheader >>
      #@nl
      #@    << Test for @lineending >>
      #@+node:<< Test for @lineending >>
      if not old.has_key("lineending") and dict.has_key("lineending"):
        
        lineending = scanAtLineendingDirective(s,dict)
        if lineending:
          self.output_newline = lineending
      #@-node:<< Test for @lineending >>
      #@nl
      #@    << Test for @pagewidth >>
      #@+node:<< Test for @pagewidth >>
      if dict.has_key("pagewidth") and not old.has_key("pagewidth"):
        
        w = scanAtPagewidthDirective(s,dict,issue_error_flag=true)
        if w and w > 0:
          self.page_width = w
      #@nonl
      #@-node:<< Test for @pagewidth >>
      #@nl
      #@    << Test for @tabwidth >>
      #@+node:<< Test for @tabwidth >>
      if dict.has_key("tabwidth") and not old.has_key("tabwidth"):
        
        w = scanAtTabwidthDirective(s,dict,issue_error_flag=true)
        if w and w != 0:
          self.tab_width = w
      
      #@-node:<< Test for @tabwidth >>
      #@nl
      old.update(dict)
      v = v.parent()
    #@  << Set current directory >>
    #@+node:<< Set current directory >>
    # This code is executed if no valid absolute path was specified in the @file node or in an @path directive.
    
    if c.frame and not self.default_directory:
      base = getBaseDirectory() # returns "" on error.
      for dir in (c.tangle_directory,c.frame.openDirectory,c.openDirectory):
        if dir and len(dir) > 0:
          dir = os_path_join(base,dir)
          if os_path_isabs(dir): # Errors may result in relative or invalid path.
            if os_path_exists(dir):
              self.default_directory = dir ; break
            else: # 9/25/02
              self.default_directory = makeAllNonExistentDirectories(dir)
    
    if not self.default_directory:
      # This should never happen: c.openDirectory should be a good last resort.
      self.error("No absolute directory specified anywhere.")
      self.default_directory = ""
    #@nonl
    #@-node:<< Set current directory >>
    #@nl
    #@  << Set comment Strings from delims >>
    #@+node:<< Set comment Strings from delims >>
    # Use single-line comments if we have a choice.
    # 8/2/01: delim1,delim2,delim3 now correspond to line,start,end
    if delim1:
      self.startSentinelComment = delim1
      self.endSentinelComment = "" # Must not be None.
    elif delim2 and delim3:
      self.startSentinelComment = delim2
      self.endSentinelComment = delim3
    else: # Emergency!
      # assert(0)
      es("Unknown language: using Python comment delimiters")
      es("c.target_language:"+`c.target_language`)
      es("delim1,delim2,delim3:" + `delim1`+":"+`delim2`+":"+`delim3`)
      self.startSentinelComment = "#" # This should never happen!
      self.endSentinelComment = ""
    #@nonl
    #@-node:<< Set comment Strings from delims >>
    #@nl
  #@nonl
  #@-node:scanAllDirectives
  #@+node:skipIndent
  # Skip past whitespace equivalent to width spaces.
  
  def skipIndent(self,s,i,width):
  
    ws = 0 ; n = len(s)
    while i < n and ws < width:
      if   s[i] == '\t': ws += (abs(self.tab_width) - (ws % abs(self.tab_width)))
      elif s[i] == ' ':  ws += 1
      else: break
      i += 1
    return i
  #@nonl
  #@-node:skipIndent
  #@+node:writeError
  def writeError(self,message):
  
    if self.errors == 0:
      es_error("errors writing: " + self.targetFileName)
  
    self.error(message)
    self.root.setOrphan()
    self.root.setDirty()
  #@nonl
  #@-node:writeError
  #@+node:old_df.rawWrite
  def rawWrite(self,root):
  
    if self.trace: trace("old_df",root)
    
    c = self.c ; self.root = root
    self.errors = 0
    self.sentinels = true # 10/1/03
    c.endEditing() # Capture the current headline.
    try:
      self.targetFileName = root.atRawFileNodeName()
      ok = self.openWriteFile(root)
      if not ok: return
      next = root.nodeAfterTree()
      #@    << write root's tree >>
      #@+node:<< write root's tree >>
      #@<< put all @first lines in root >>
      #@+node:<< put all @first lines in root >> (3.x)
      #@+at 
      #@nonl
      # Write any @first lines.  These lines are also converted to 
      # @verbatim lines, so the read logic simply ignores lines 
      # preceding the @+leo sentinel.
      #@-at
      #@@c
      
      s = root.t.bodyString
      tag = "@first"
      i = 0
      while match(s,i,tag):
        i += len(tag)
        i = skip_ws(s,i)
        j = i
        i = skip_to_end_of_line(s,i)
        # 21-SEP-2002 DTHEIN: write @first line, whether empty or not
        line = s[j:i]
        self.putBuffered(line) ; self.onl()
        i = skip_nl(s,i)
      #@nonl
      #@-node:<< put all @first lines in root >> (3.x)
      #@nl
      self.putOpenLeoSentinel("@+leo")
      #@<< put optional @comment sentinel lines >>
      #@+node:<< put optional @comment sentinel lines >>
      s2 = app.config.output_initial_comment
      if s2:
        lines = string.split(s2,"\\n")
        for line in lines:
          line = line.replace("@date",time.asctime())
          if len(line)> 0:
            self.putSentinel("@comment " + line)
      #@-node:<< put optional @comment sentinel lines >>
      #@nl
      
      v = root
      while v and v != next:
        #@  << Write v's node >>
        #@+node:<< Write v's node >>
        self.putOpenNodeSentinel(v)
          
        s = v.bodyString()
        if s and len(s) > 0:
          self.putSentinel("@+body")
          if self.newline_pending:
            self.newline_pending = false
            self.onl()
          s = toEncodedString(s,self.encoding,reportErrors=true) # 3/7/03
          self.outputStringWithLineEndings(s)
          self.putSentinel("@-body")
          
        self.putCloseNodeSentinel(v)
        #@-node:<< Write v's node >>
        #@nl
        v = v.threadNext()
      
      self.putSentinel("@-leo")
      #@<< put all @last lines in root >>
      #@+node:<< put all @last lines in root >> (3.x)
      #@+at 
      #@nonl
      # Write any @last lines.  These lines are also converted to 
      # @verbatim lines, so the read logic simply ignores lines 
      # following the @-leo sentinel.
      #@-at
      #@@c
      
      tag = "@last"
      lines = string.split(root.t.bodyString,'\n')
      n = len(lines) ; j = k = n - 1
      # Don't write an empty last line.
      if j >= 0 and len(lines[j])==0:
        j = k = n - 2
      # Scan backwards for @last directives.
      while j >= 0:
        line = lines[j]
        if match(line,0,tag): j -= 1
        else: break
      # Write the @last lines.
      for line in lines[j+1:k+1]:
        i = len(tag) ; i = skip_ws(line,i)
        self.putBuffered(line[i:]) ; self.onl()
      #@nonl
      #@-node:<< put all @last lines in root >> (3.x)
      #@nl
      #@nonl
      #@-node:<< write root's tree >>
      #@nl
      self.closeWriteFile()
      self.replaceTargetFileIfDifferent()
      root.clearOrphan() ; root.clearDirty()
    except:
      self.handleWriteException(root)
  #@-node:old_df.rawWrite
  #@+node:old_df.silentWrite
  def silentWrite(self,root):
  
    if self.trace: trace("old_df",root)
  
    c = self.c ; self.root = root
    self.errors = 0
    c.endEditing() # Capture the current headline.
    try:
      self.targetFileName = root.atSilentFileNodeName()
      ok = self.openWriteFile(root)
      if not ok: return
      next = root.nodeAfterTree()
      v = root
      while v and v != next:
        #@      << Write v's headline if it starts with @@ >>
        #@+node:<< Write v's headline if it starts with @@ >>
        s = v.headString()
        if match(s,0,"@@"):
          s = s[2:]
          if s and len(s) > 0:
            s = toEncodedString(s,self.encoding,reportErrors=true) # 3/7/03
            self.outputFile.write(s)
        #@-node:<< Write v's headline if it starts with @@ >>
        #@nl
        #@      << Write v's body >>
        #@+node:<< Write v's body >>
        s = v.bodyString()
        if s and len(s) > 0:
          s = toEncodedString(s,self.encoding,reportErrors=true) # 3/7/03
          self.outputStringWithLineEndings(s)
        #@nonl
        #@-node:<< Write v's body >>
        #@nl
        v = v.threadNext()
      self.closeWriteFile()
      self.replaceTargetFileIfDifferent()
      root.clearOrphan() ; root.clearDirty()
    except:
      self.handleWriteException(root)
  #@nonl
  #@-node:old_df.silentWrite
  #@+node:old_df.write
  # This is the entry point to the write code.  root should be an @file vnode.
  
  def write(self,root,nosentinels=false):
    
    if self.trace: trace("old_df",root)
    
    # Remove any old tnodeList.
    if hasattr(root,"tnodeList"):
      if self.trace: trace("removing tnodeList for " + `root`)
      delattr(root,"tnodeList")
  
    c = self.c
    self.sentinels = not nosentinels
    #@  << initialize >>
    #@+node:<< initialize >>
    self.errors = 0 # 9/26/02
    c.setIvarsFromPrefs()
    self.root = root
    self.raw = false
    c.endEditing() # Capture the current headline.
    #@nonl
    #@-node:<< initialize >>
    #@nl
    try:
      #@    << open the file; return on error >>
      #@+node:<< open the file; return on error >>
      if nosentinels:
        self.targetFileName = root.atNoSentinelsFileNodeName()
      else:
        self.targetFileName = root.atFileNodeName()
      
      ok = self.openWriteFile(root)
      if not ok: return
      #@nonl
      #@-node:<< open the file; return on error >>
      #@nl
      root.clearVisitedInTree()
      #@    << write then entire @file tree >>
      #@+node:<< write then entire @file tree >> (3.x)
      next = root.nodeAfterTree()
      
      #@<< put all @first lines in root >>
      #@+node:<< put all @first lines in root >> (3.x)
      #@+at 
      #@nonl
      # Write any @first lines.  These lines are also converted to 
      # @verbatim lines, so the read logic simply ignores lines 
      # preceding the @+leo sentinel.
      #@-at
      #@@c
      
      s = root.t.bodyString
      tag = "@first"
      i = 0
      while match(s,i,tag):
        i += len(tag)
        i = skip_ws(s,i)
        j = i
        i = skip_to_end_of_line(s,i)
        # 21-SEP-2002 DTHEIN: write @first line, whether empty or not
        line = s[j:i]
        self.putBuffered(line) ; self.onl()
        i = skip_nl(s,i)
      #@nonl
      #@-node:<< put all @first lines in root >> (3.x)
      #@nl
      #@<< write the derived file >>
      #@+node:<< write the derived file>>
      tag1 = "@+leo"
      
      self.putOpenLeoSentinel(tag1)
      self.putInitialComment()
      self.putOpenNodeSentinel(root)
      self.putBodyPart(root)
      self.putCloseNodeSentinel(root)
      self.putSentinel("@-leo")
      #@nonl
      #@-node:<< write the derived file>>
      #@nl
      #@<< put all @last lines in root >>
      #@+node:<< put all @last lines in root >> (3.x)
      #@+at 
      #@nonl
      # Write any @last lines.  These lines are also converted to 
      # @verbatim lines, so the read logic simply ignores lines 
      # following the @-leo sentinel.
      #@-at
      #@@c
      
      tag = "@last"
      lines = string.split(root.t.bodyString,'\n')
      n = len(lines) ; j = k = n - 1
      # Don't write an empty last line.
      if j >= 0 and len(lines[j])==0:
        j = k = n - 2
      # Scan backwards for @last directives.
      while j >= 0:
        line = lines[j]
        if match(line,0,tag): j -= 1
        else: break
      # Write the @last lines.
      for line in lines[j+1:k+1]:
        i = len(tag) ; i = skip_ws(line,i)
        self.putBuffered(line[i:]) ; self.onl()
      #@nonl
      #@-node:<< put all @last lines in root >> (3.x)
      #@nl
      
      root.setVisited()
      #@nonl
      #@-node:<< write then entire @file tree >> (3.x)
      #@nl
      self.closeWriteFile()
      if not nosentinels:
        #@      << warn about @ignored and orphans >>
        #@+node:<< Warn about @ignored and orphans  >>
        # 10/26/02: Always warn, even when language=="cweb"
        
        next = root.nodeAfterTree()
        v = root
        while v and v != next:
          if not v.isVisited():
            self.writeError("Orphan node:  " + v.headString())
          if v.isAtIgnoreNode():
            self.writeError("@ignore node: " + v.headString())
          v = v.threadNext()
        #@-node:<< Warn about @ignored and orphans  >>
        #@nl
      #@    << finish writing >>
      #@+node:<< finish writing >>
      #@+at 
      #@nonl
      # We set the orphan and dirty flags if there are problems writing 
      # the file to force Commands::write_LEO_file to write the tree to 
      # the .leo file.
      #@-at
      #@@c
      
      if self.errors > 0 or self.root.isOrphan():
        root.setOrphan()
        root.setDirty() # 2/9/02: make _sure_ we try to rewrite this file.
        os.remove(self.outputFileName) # Delete the temp file.
        es("Not written: " + self.outputFileName)
      else:
        root.clearOrphan()
        root.clearDirty()
        self.replaceTargetFileIfDifferent()
      #@nonl
      #@-node:<< finish writing >>
      #@nl
    except:
      self.handleWriteException()
  #@-node:old_df.write
  #@+node:atFile.closeWriteFile
  def closeWriteFile (self):
    
    if self.outputFile:
      if self.suppress_newlines and self.newline_pending:
        self.newline_pending = false
        self.onl() # Make sure file ends with a newline.
      self.outputFile.flush()
      self.outputFile.close()
      self.outputFile = None
  #@-node:atFile.closeWriteFile
  #@+node:atFile.handleWriteException
  def handleWriteException (self,root=None):
    
    es("exception writing:" + self.targetFileName,color="red")
    es_exception()
    
    if self.outputFile:
      self.outputFile.flush()
      self.outputFile.close()
      self.outputFile = None
    
    if self.outputFileName != None:
      try: # Just delete the temp file.
        os.remove(self.outputFileName)
      except:
        es("exception deleting:" + self.outputFileName,color="red")
        es_exception()
  
    if root:
      # Make sure we try to rewrite this file.
      root.setOrphan()
      root.setDirty()
  #@nonl
  #@-node:atFile.handleWriteException
  #@+node:atFile.openWriteFile
  # Open files.  Set root.orphan and root.dirty flags and return on errors.
  
  def openWriteFile (self,root):
  
    try:
      self.scanAllDirectives(root)
      valid = self.errors == 0
    except:
      self.writeError("exception in atFile.scanAllDirectives")
      es_exception()
      valid = false
  
    if valid:
      try:
        fn = self.targetFileName
        self.shortFileName = fn # name to use in status messages.
        self.targetFileName = os_path_join(self.default_directory,fn)
        self.targetFileName = os_path_normpath(self.targetFileName)
        path = os_path_dirname(self.targetFileName)
        if not path or not os_path_exists(path):
          self.writeError("path does not exist: " + path)
          valid = false
      except:
        self.writeError("exception creating path:" + fn)
        es_exception()
        valid = false
  
    if valid and os_path_exists(self.targetFileName):
      try:
        if not os.access(self.targetFileName,os.W_OK):
          self.writeError("read only: " + self.targetFileName)
          valid = false
      except:
        pass # os.access() may not exist on all platforms.
      
    if valid:
      try:
        self.outputFileName = self.targetFileName + ".tmp"
        self.outputFile = open(self.outputFileName,'wb')
        if self.outputFile is None:
          self.writeError("can not open " + self.outputFileName)
          valid = false
      except:
        es("exception opening:" + self.outputFileName)
        es_exception()
        valid = false
    
    if not valid:
      root.setOrphan()
      root.setDirty()
    
    return valid
  #@nonl
  #@-node:atFile.openWriteFile
  #@+node:atFile.putInitialComment
  def putInitialComment (self):
    
    s2 = app.config.output_initial_comment
    if s2:
      lines = string.split(s2,"\\n")
      for line in lines:
        line = line.replace("@date",time.asctime())
        if len(line)> 0:
          self.putSentinel("@comment " + line)
  #@nonl
  #@-node:atFile.putInitialComment
  #@+node:atFile.replaceTargetFileIfDifferent
  def replaceTargetFileIfDifferent (self):
    
    assert(self.outputFile == None)
    
    self.fileChangedFlag = false
    if os_path_exists(self.targetFileName):
      if filecmp.cmp(self.outputFileName,self.targetFileName):
        #@      << delete the output file >>
        #@+node:<< delete the output file >>
        try: # Just delete the temp file.
          os.remove(self.outputFileName)
        except:
          es("exception deleting:" + self.outputFileName)
          es_exception()
        
        es("unchanged: " + self.shortFileName)
        #@nonl
        #@-node:<< delete the output file >>
        #@nl
      else:
        #@      << replace the target file with the output file >>
        #@+node:<< replace the target file with the output file >>
        try:
          # 10/6/02: retain the access mode of the previous file,
          # removing any setuid, setgid, and sticky bits.
          mode = (os.stat(self.targetFileName))[0] & 0777
        except:
          mode = None
        
        try: # Replace target file with temp file.
          os.remove(self.targetFileName)
          try:
            utils_rename(self.outputFileName,self.targetFileName)
            if mode != None: # 10/3/02: retain the access mode of the previous file.
              try:
                os.chmod(self.targetFileName,mode)
              except:
                es("exception in os.chmod(%s)" % (self.targetFileName))
            es("writing: " + self.shortFileName)
            self.fileChangedFlag = true
          except:
            # 6/28/03
            self.writeError("exception renaming: %s to: %s" % (self.outputFileName,self.targetFileName))
            es_exception()
        except:
          self.writeError("exception removing:" + self.targetFileName)
          es_exception()
          try: # Delete the temp file when the deleting the target file fails.
            os.remove(self.outputFileName)
          except:
            es("exception deleting:" + self.outputFileName)
            es_exception()
        #@nonl
        #@-node:<< replace the target file with the output file >>
        #@nl
    else:
      #@    << rename the output file to be the target file >>
      #@+node:<< rename the output file to be the target file >>
      try:
        utils_rename(self.outputFileName,self.targetFileName)
        es("creating: " + self.targetFileName)
        self.fileChangedFlag = true
      except:
        self.writeError("exception renaming:" + self.outputFileName +
          " to " + self.targetFileName)
        es_exception()
      #@nonl
      #@-node:<< rename the output file to be the target file >>
      #@nl
  #@-node:atFile.replaceTargetFileIfDifferent
  #@+node:atFile.outputStringWithLineEndings
  # Write the string s as-is except that we replace '\n' with the proper line ending.
  
  def outputStringWithLineEndings (self,s):
  
    # Calling self.onl() runs afoul of queued newlines.
    self.os(s.replace('\n',self.output_newline))
  #@nonl
  #@-node:atFile.outputStringWithLineEndings
  #@+node:putBodyPart (3.x)
  def putBodyPart(self,v):
    
    """ Generate the body enclosed in sentinel lines."""
  
    s = v.t.bodyString
    i = skip_ws_and_nl(s, 0)
    if i >= len(s): return
  
    s = removeTrailingWs(s) # don't use string.rstrip!
    self.putSentinel("@+body")
    #@  << put code/doc parts and sentinels >>
    #@+node:<< put code/doc parts and sentinels >> (3.x)
    i = 0 ; n = len(s)
    firstLastHack = 1
    
    if firstLastHack:
      #@  << initialize lookingForFirst/Last & initialLastDirective >>
      #@+node:<< initialize lookingForFirst/Last & initialLastDirective >>
      # 14-SEP-2002 DTHEIN: If this is the root node, then handle all @first directives here
      lookingForLast = 0
      lookingForFirst = 0
      initialLastDirective = -1
      lastDirectiveCount = 0
      if (v == self.root):
        lookingForLast = 1
        lookingForFirst = 1
      #@nonl
      #@-node:<< initialize lookingForFirst/Last & initialLastDirective >>
      #@nl
    while i < n:
      kind = self.directiveKind(s,i)
      if firstLastHack:
        #@    << set lookingForFirst/Last & initialLastDirective >>
        #@+node:<< set lookingForFirst/Last & initialLastDirective >>
        # 14-SEP-2002 DTHEIN: If first directive isn't @first, then stop looking for @first
        if lookingForFirst:
          if kind != miscDirective:
            lookingForFirst = 0
          elif not match_word(s,i,"@first"):
            lookingForFirst = 0
        
        if lookingForLast:
          if initialLastDirective == -1:
            if (kind == miscDirective) and match_word(s,i,"@last"):
              # mark the point where the last directive was found
              initialLastDirective = i
          else:
            if (kind != miscDirective) or (not match_word(s,i,"@last")):
              # found something after @last, so process the @last directives
              # in 'ignore them' mode
              i, initialLastDirective = initialLastDirective, -1
              lastDirectiveCount = 0
              kind = self.directiveKind(s,i)
        #@nonl
        #@-node:<< set lookingForFirst/Last & initialLastDirective >>
        #@nl
      j = i
      if kind == docDirective or kind == atDirective:
        i = self.putDoc(s,i,kind)
      elif ( # 10/16/02
        kind == miscDirective or
        kind == rawDirective or
        kind == endRawDirective ):
        if firstLastHack:
          #@      << handle misc directives >>
          #@+node:<< handle misc directives >>
          if lookingForFirst: # DTHEIN: can only be true if it is @first directive
            i = self.putEmptyDirective(s,i)
          elif (initialLastDirective != -1) and match_word(s,i,"@last"):
            # DTHEIN: can only be here if lookingForLast is true
            # skip the last directive ... we'll output it at the end if it
            # is truly 'last'
            lastDirectiveCount += 1
            i = skip_line(s,i)
          else:
            i = self.putDirective(s,i)
          #@nonl
          #@-node:<< handle misc directives >>
          #@nl
        else:
          i = self.putDirective(s,i)
      elif kind == noDirective or kind == othersDirective:
        i = self.putCodePart(s,i,v)
      elif kind == cDirective or kind == codeDirective:
        i = self.putDirective(s,i)
        i = self.putCodePart(s,i,v)
      else: assert(false) # We must handle everything that directiveKind returns
      assert(n == len(s))
      assert(j < i) # We must make progress.
    
    if firstLastHack:
      #@  << put out the last directives, if any >>
      #@+node:<< put out the last directives, if any >>
      # 14-SEP-2002 DTHEIN
      if initialLastDirective != -1:
        d = initialLastDirective
        for k in range(lastDirectiveCount):
          d = self.putEmptyDirective(s,d)
      #@nonl
      #@-node:<< put out the last directives, if any >>
      #@nl
    #@nonl
    #@-node:<< put code/doc parts and sentinels >> (3.x)
    #@nl
    self.putSentinel("@-body")
  #@nonl
  #@-node:putBodyPart (3.x)
  #@+node:putDoc
  def putDoc(self,s,i,kind):
  
    """Outputs a doc section terminated by @code or end-of-text.
    
    All other interior directives become part of the doc part."""
  
    if kind == atDirective:
      i += 1 ; tag = "at"
    elif kind == docDirective:
      i += 4 ; tag = "doc"
    else: assert(false)
    # Set j to the end of the doc part.
    n = len(s) ; j = i
    while j < n:
      j = skip_line(s, j)
      kind = self.directiveKind(s, j)
      if kind == codeDirective or kind == cDirective:
        break
    self.putSentinel("@+" + tag)
    self.putDocPart(s[i:j])
    self.putSentinel("@-" + tag)
    return j
  #@-node:putDoc
  #@+node:putDocPart (3.x)
  # Puts a comment part in comments.
  # Note: this routine is _never_ called in cweb mode,
  # so noweb section references are _valid_ in cweb doc parts!
  
  def putDocPart(self,s):
  
    # j = skip_line(s,0) ; trace(`s[:j]`)
    single = len(self.endSentinelComment) == 0
    if not single:
      self.putIndent(self.indent)
      self.os(self.startSentinelComment) ; self.onl()
    # Put all lines.
    i = 0 ; n = len(s)
    while i < n:
      self.putIndent(self.indent)
      leading = self.indent
      if single:
        self.os(self.startSentinelComment) ; self.oblank()
        leading += len(self.startSentinelComment) + 1
      #@    << copy words, splitting the line if needed >>
      #@+node:<< copy words, splitting the line if needed >>
      #@+at 
      #@nonl
      # We remove trailing whitespace from lines that have _not_ been 
      # split so that a newline has been inserted by this routine if and 
      # only if it is preceded by whitespace.
      #@-at
      #@@c
      
      line = i # Start of the current line.
      while i < n:
        word = i # Start of the current word.
        # Skip the next word and trailing whitespace.
        i = skip_ws(s, i)
        while i < n and not is_nl(s,i) and not is_ws(s[i]):
          i += 1
        i = skip_ws(s,i)
        # Output the line if no more is left.
        if i < n and is_nl(s,i):
          break
        # Split the line before the current word if needed.
        lineLen = i - line
        if line == word or leading + lineLen < self.page_width:
          word = i # Advance to the next word.
        else:
          # Write the line before the current word and insert a newline.
          theLine = s[line:word]
          self.os(theLine)
          self.onl() # This line must contain trailing whitespace.
          line = i = word  # Put word on the next line.
          break
      # Remove trailing whitespace and output the remainder of the line.
      theLine = string.rstrip(s[line:i]) # from right.
      self.os(theLine)
      if i < n and is_nl(s,i):
        i = skip_nl(s,i)
        self.onl() # No inserted newline and no trailing whitespace.
      #@nonl
      #@-node:<< copy words, splitting the line if needed >>
      #@nl
    if not single:
      # This comment is like a sentinel.
      self.onl() ; self.putIndent(self.indent)
      self.os(self.endSentinelComment)
      self.onl() # Note: no trailing whitespace.
  #@nonl
  #@-node:putDocPart (3.x)
  #@+node:putCodePart & allies (3.x)
  def putCodePart(self,s,i,v):
  
    """Expands a code part, terminated by any at-directive except at-others.
    
    It expands references and at-others and outputs @verbatim sentinels as needed."""
  
    atOthersSeen = false # true: at-others has been expanded.
    while i < len(s):
      #@    << handle the start of a line >>
      #@+node:<< handle the start of a line >>
      #@+at 
      #@nonl
      # The at-others directive is the only directive that is recognized 
      # following leading whitespace, so it is just a little tricky to 
      # recognize it.
      #@-at
      #@@c
      
      leading_nl = (s[i] == body_newline) # 9/27/02: look ahead before outputting newline.
      if leading_nl:
        i = skip_nl(s,i)
        self.onl() # 10/15/02: simpler to do it here.
      
      #leading_ws1 = i # 1/27/03
      j,delta = skip_leading_ws_with_indent(s,i,self.tab_width)
      #leading_ws2 = j # 1/27/03
      kind1 = self.directiveKind(s,i)
      kind2 = self.directiveKind(s,j)
      if self.raw:
        if kind1 == endRawDirective:
          #@    << handle @end_raw >>
          #@+node:<< handle @end_raw >>
          self.raw = false
          self.putSentinel("@@end_raw")
          i = skip_line(s,i)
          #@nonl
          #@-node:<< handle @end_raw >>
          #@nl
      else:
        if kind1 == othersDirective or kind2 == othersDirective:
          #@    << handle @others >>
          #@+node:<< handle @others >>
          # This skips all indent and delta whitespace, so putAtOthers must generate it all.
          
          if 0: # 9/27/02: eliminates the newline preceeding the @+others sentinel.
            # This does not seem to be a good idea.
            i = skip_line(s,i) 
          else:
            i = skip_to_end_of_line(s,i)
          
          if atOthersSeen:
            self.writeError("@others already expanded in: " + v.headString())
          else:
            atOthersSeen = true
            self.putAtOthers(v, delta)
            
            # 12/8/02: Skip the newline _after_ the @others.
            if not self.sentinels and is_nl(s,i):
              i = skip_nl(s,i)
          #@-node:<< handle @others >>
          #@nl
        elif kind1 == rawDirective:
          #@    << handle @raw >>
          #@+node:<< handle @raw >>
          self.raw = true
          self.putSentinel("@@raw")
          i = skip_line(s,i)
          #@nonl
          #@-node:<< handle @raw >>
          #@nl
        elif kind1 == noDirective:
          #@    << put @verbatim sentinel if necessary >>
          #@+node:<< put @verbatim sentinel if necessary >>
          if match (s,i,self.startSentinelComment + '@'):
            self.putSentinel("@verbatim") # Bug fix (!!): 9/20/03
          #@nonl
          #@-node:<< put @verbatim sentinel if necessary >>
          #@nl
        else:
          break # all other directives terminate the code part.
      #@nonl
      #@-node:<< handle the start of a line >>
      #@nl
      #@    << put the line >>
      #@+node:<< put the line >>
      if not self.raw:
        # 12/8/02: Don't write trailing indentation if not writing sentinels.
        if self.sentinels or j < len(s):
          self.putIndent(self.indent)
      
      newlineSeen = false
      # 12/8/02: we buffer characters here for two reasons:
      # 1) to make traces easier to read and 2) to increase speed.
      buf = i # Indicate the start of buffered characters.
      while i < len(s) and not newlineSeen:
        ch = s[i]
        if ch == body_newline:
          break
        elif ch == body_ignored_newline:
          i += 1
        elif ch == '<' and not self.raw:
          #@    << put possible section reference >>
          #@+node:<< put possible section reference >>
          isSection, j = self.isSectionName(s, i)
          
          if isSection:
            # Output the buffered characters and clear the buffer.
            s2 = s[buf:i] ; buf = i
            # 7/9/03: don't output trailing indentation if we aren't generating sentinels.
            if not self.sentinels:
              while len(s2) and s2[-1] in (' ','\t'):
                s2 = s2[:-1]
            self.putBuffered(s2)
            # Output the expansion.
            name = s[i:j]
            j,newlineSeen = self.putRef(name,v,s,j,delta)
            assert(j > i) # isSectionName must have made progress
            i = j ; buf = i
          else:
            # This is _not_ an error.
            i += 1
          #@nonl
          #@-node:<< put possible section reference >>
          #@nl
        else:
          i += 1
      # Output any buffered characters.
      self.putBuffered(s[buf:i])
      #@nonl
      #@-node:<< put the line >>
      #@nl
  
    # Raw code parts can only end at the end of body text.
    self.raw = false
    return i
  #@-node:putCodePart & allies (3.x)
  #@+node:inAtOthers
  def inAtOthers(self,v):
  
    """Returns true if v should be included in the expansion of the at-others directive in the body text of v's parent.
    
    v will not be included if it is a definition node or if its body text contains an @ignore directive.
    Previously, a "nested" @others directive would also inhibit the inclusion of v."""
  
    # Return false if this has been expanded previously.
    if  v.isVisited(): return false
  
    # Return false if this is a definition node.
    h = v.headString()
    i = skip_ws(h,0)
    isSection, j = self.isSectionName(h,i)
    if isSection: return false
  
    # Return false if v's body contains an @ignore or at-others directive.
    if 1: # 7/29/02: New code.  Amazingly, this appears to work!
      return not v.isAtIgnoreNode()
    else: # old & reliable code
      return not v.isAtIgnoreNode() and not v.isAtOthersNode()
  #@nonl
  #@-node:inAtOthers
  #@+node:isSectionName
  # returns (flag, end). end is the index of the character after the section name.
  
  def isSectionName(self,s,i):
  
    if not match(s,i,"<<"):
      return false, -1
    i = find_on_line(s,i,">>")
    if i:
      return true, i + 2
    else:
      return false, -1
  #@nonl
  #@-node:isSectionName
  #@+node:putAtOthers
  #@+at 
  #@nonl
  # The at-others directive is recognized only at the start of the line.  
  # This code must generate all leading whitespace for the opening sentinel.
  #@-at
  #@@c
  def putAtOthers(self,v,delta):
    
    """Output code corresponding to an @others directive."""
  
    self.indent += delta
    self.putSentinel("@+others")
  
    child = v.firstChild()
    while child:
      if self.inAtOthers( child ):
        self.putAtOthersChild( child )
      child = child.next()
  
    self.putSentinel("@-others")
    self.indent -= delta
  #@nonl
  #@-node:putAtOthers
  #@+node:putAtOthersChild
  def putAtOthersChild(self,v):
    
    # trace("%d %s" % (self.indent,`v`))
    self.putOpenNodeSentinel(v)
    
    # Insert the expansion of v.
    v.setVisited() # Make sure it is never expanded again.
    self.putBodyPart(v)
  
    # Insert expansions of all children.
    child = v.firstChild()
    while child:
      if self.inAtOthers( child ):
        self.putAtOthersChild( child )
      child = child.next()
  
    self.putCloseNodeSentinel(v)
  #@-node:putAtOthersChild
  #@+node:putRef
  def putRef (self,name,v,s,i,delta):
  
    newlineSeen = false
    ref = findReference(name, v)
    if not ref:
      self.writeError("undefined section: " + name +
        "\n\treferenced from: " + v.headString())
      return i,newlineSeen
  
    # trace(self.indent,delta,s[i:])
    #@  << Generate the expansion of the reference >>
    #@+node:<< Generate the expansion of the reference >>
    # Adjust indent here so sentinel looks better.
    self.indent += delta
    
    self.putSentinel("@" + name)
    self.putOpenSentinels(v,ref)
    self.putBodyPart(ref)
    self.putCloseSentinels(v,ref)
    #@<< Add @verbatimAfterRef sentinel if required >>
    #@+node:<< Add @verbatimAfterRef sentinel if required >>
    j = skip_ws(s,i)
    if j < len(s) and match(s,j,self.startSentinelComment + '@'):
      self.putSentinel("@verbatimAfterRef")
      # 9/27/02: Put the line immediately, before the @-node sentinel.
      k = skip_to_end_of_line(s,i)
      self.putBuffered(s[i:k])
      i = k ; newlineSeen = false
    #@nonl
    #@-node:<< Add @verbatimAfterRef sentinel if required >>
    #@nl
    
    self.indent -= delta
    ref.setVisited()
    #@nonl
    #@-node:<< Generate the expansion of the reference >>
    #@nl
  
    # The newlineSeen allows the caller to break out of the loop.
    return i,newlineSeen
  #@nonl
  #@-node:putRef
  #@+node:putBuffered
  def putBuffered (self,s):
    
    """Put s, converting all tabs to blanks as necessary."""
    
    if s:
      w = self.tab_width
      if w < 0:
        lines = s.split('\n')
        for i in xrange(len(lines)):
          line = lines[i]
          line2 = ""
          for j in xrange(len(line)):
            ch = line[j]
            if ch == '\t':
              w2 = computeWidth(s[:j],w)
              w3 = (abs(w) - (w2 % abs(w)))
              line2 += ' ' * w3
            else:
              line2 += ch
          lines[i] = line2
        s = string.join(lines,'\n')
      self.os(s)
  #@nonl
  #@-node:putBuffered
  #@+node:os, onl, etc. (leoAtFile)
  def oblank(self):
    self.os(' ')
  
  def oblanks(self,n):
    self.os(' ' * abs(n))
  
  def onl(self):
    self.os(self.output_newline)
  
  def os(self,s):
    if s is None or len(s) == 0: return
    if self.suppress_newlines and self.newline_pending:
      self.newline_pending = false
      s = self.output_newline + s
    if self.outputFile:
      try:
        s = toEncodedString(s,self.encoding,reportErrors=true)
        self.outputFile.write(s)
      except:
        es("exception writing:" + `s`)
        es_exception()
  
  def otabs(self,n):
    self.os('\t' * abs(n))
  #@nonl
  #@-node:os, onl, etc. (leoAtFile)
  #@+node:putDirective  (handles @delims) 3.x
  # This method outputs s, a directive or reference, in a sentinel.
  
  def putDirective(self,s,i):
  
    tag = "@delims"
    assert(i < len(s) and s[i] == '@')
    k = i
    j = skip_to_end_of_line(s,i)
    directive = s[i:j]
  
    if match_word(s,k,tag):
      #@    << handle @delims >>
      #@+node:<< handle @delims >>
      # Put a space to protect the last delim.
      self.putSentinel(directive + " ") # 10/23/02: put @delims, not @@delims
      
      # Skip the keyword and whitespace.
      j = i = skip_ws(s,k+len(tag))
      
      # Get the first delim.
      while i < len(s) and not is_ws(s[i]) and not is_nl(s,i):
        i += 1
      if j < i:
        self.startSentinelComment = s[j:i]
        # Get the optional second delim.
        j = i = skip_ws(s,i)
        while i < len(s) and not is_ws(s[i]) and not is_nl(s,i):
          i += 1
        self.endSentinelComment = choose(j<i, s[j:i], "")
      else:
        self.writeError("Bad @delims directive")
      #@nonl
      #@-node:<< handle @delims >>
      #@nl
    else:
      self.putSentinel("@" + directive)
  
    i = skip_line(s,k)
    return i
  #@nonl
  #@-node:putDirective  (handles @delims) 3.x
  #@+node:putEmptyDirective (Dave Hein)
  # 14-SEP-2002 DTHEIN
  # added for use by putBodyPart()
  
  # This method outputs the directive without the parameter text
  def putEmptyDirective(self,s,i):
  
    assert(i < len(s) and s[i] == '@')
    
    endOfLine = s.find('\n',i)
    # 21-SEP-2002 DTHEIN: if no '\n' then just use line length
    if endOfLine == -1:
      endOfLine = len(s)
    token = s[i:endOfLine].split()
    directive = token[0]
    self.putSentinel("@" + directive)
  
    i = skip_line(s,i)
    return i
  #@nonl
  #@-node:putEmptyDirective (Dave Hein)
  #@+node:putIndent
  def putIndent(self,n):
    
    """Put tabs and spaces corresponding to n spaces, assuming that we are at the start of a line."""
  
    if n != 0:
      w = self.tab_width
      if w > 1:
        q,r = divmod(n,w) 
        self.otabs(q) 
        self.oblanks(r)
      else:
        self.oblanks(n)
  #@nonl
  #@-node:putIndent
  #@-others
  #@nonl
  #@-node:<< class baseOldDerivedFile methods >>
  #@nl
  
class oldDerivedFile(baseOldDerivedFile):
  pass # May be overridden in plugins.
  
class baseNewDerivedFile(oldDerivedFile):
  """The base class to read and write 4.x derived files."""
  #@  << class baseNewDerivedFile methods >>
  #@+node:<< class baseNewDerivedFile methods >>
  #@+others
  #@+node:newDerivedFile.__init__
  def __init__(self,c):
    
    """Ctor for 4.x atFile class."""
    
    at = self
  
    # Initialize the base class.
    oldDerivedFile.__init__(self,c) 
  
    # For 4.x reading & writing...
    at.inCode = true
    ## at.nodeIndices = app.nodeIndices
  
    # For 4.x writing...
    at.docKind = None
    at.pending = [] # Doc part that remains to be written.
  
    # For 4.x reading...
    at.docOut = [] # The doc part being accumulated.
    at.done = false # true when @-leo seen.
    at.endSentinelStack = []
    at.importing = false
    at.indent = 0 ; at.indentStack = []
    at.lastLines = [] # The lines after @-leo
    at.leadingWs = ""
    at.out = None ; at.outStack = []
    at.root_seen = false # true: root vnode has been handled in this file.
    at.tnodeList = [] ; at.tnodeListIndex = 0
    at.t = None ; at.tStack = []
  
    # The dispatch dictionary used by scanText4.
    at.dispatch_dict = {
      # Plain line.
      noSentinel: at.readNormalLine,
      # Starting sentinels...
      startAt:     at.readStartAt,
      startDoc:    at.readStartDoc,
      startLeo:    at.readStartLeo,
      startNode:   at.readStartNode,
      startOthers: at.readStartOthers,
      # Ending sentinels...
      endAt:     at.readEndAt,
      endDoc:    at.readEndDoc,
      endLeo:    at.readEndLeo,
      endNode:   at.readEndNode,
      endOthers: at.readEndOthers,
      # Non-paired sentinels.
      startAfterRef:  at.readAfterRef,
      startComment:   at.readComment,
      startDelims:    at.readDelims,
      startDirective: at.readDirective,
      startNl:        at.readNl,
      startNonl:      at.readNonl,
      startRef:       at.readRef,
      startVerbatim:  at.readVerbatim,
      # Ignored 3.x sentinels
      endBody:               at.ignoreOldSentinel,
      startBody:             at.ignoreOldSentinel,
      startVerbatimAfterRef: at.ignoreOldSentinel }
  #@nonl
  #@-node:newDerivedFile.__init__
  #@+node:new_df.readOpenFile
  def readOpenFile(self,root,file,firstLines):
    
    """Read an open 4.x derived file."""
    
    at = self
    if at.trace: trace("new_df",root)
  
    # Scan the 4.x file.
    at.scanAllDirectives(root)
    at.tnodeListIndex = 0
    lastLines = at.scanText4(file,root)
    root.t.setVisited() # Disable warning about set nodes.
    
    # Handle first and last lines.
    try: body = root.t.tempBodyString
    except: body = ""
    lines = body.split('\n')
    at.completeFirstDirectives(lines,firstLines)
    at.completeLastDirectives(lines,lastLines)
    s = '\n'.join(lines).replace('\r', '')
    root.t.tempBodyString = s
  #@nonl
  #@-node:new_df.readOpenFile
  #@+node:findChild
  def findChild (self,headline):
    
    """Return the next tnode in the at.tnodeList."""
  
    at = self
  
    if at.importing:
      v = at.createImportedNode(at.root,at.c,headline)
      return v.t
  
    if not hasattr(at.root,"tnodeList"):
      at.readError("no tnodeList for " + `at.root`)
      es("Write the @file node or use the Import Derived File command")
      trace("no tnodeList for ",at.root)
      return None
      
    if at.tnodeListIndex >= len(at.root.tnodeList):
      at.readError("bad tnodeList index: %d, %s" % (at.tnodeListIndex,`at.root`))
      trace("bad tnodeList index",at.tnodeListIndex,len(at.root.tnodeList),at.root)
      return None
      
    t = at.root.tnodeList[at.tnodeListIndex]
    assert(t)
    at.tnodeListIndex += 1
  
    # Get any vnode joined to t.
    # To do: cut/paste may cause problems here...
    try:
      v = t.joinList[0]
    except:
      at.readError("No joinList for tnode: %s" % `t`)
      trace(at.tnodeListIndex,len(at.root.tnodeList))
      return None
  
    # Check the headline.
    if headline.strip() == v.headString().strip():
      t.setVisited() # Supress warning about unvisited node.
      return t
    else:
      at.readError(
        "Mismatched headline.\nExpecting: %s\ngot: %s" %
        (headline,v.headString()))
      trace("Mismatched headline",headline,v.headString())
      trace(at.tnodeListIndex,len(at.root.tnodeList))
      return None
  #@-node:findChild
  #@+node:scanText4 & allies
  def scanText4 (self,file,v):
    
    """Scan a 4.x derived file non-recursively."""
  
    at = self
    #@  << init ivars for scanText4 >>
    #@+node:<< init ivars for scanText4 >>
    # Unstacked ivars...
    at.done = false
    at.inCode = true
    at.lastLines = [] # The lines after @-leo
    at.leadingWs = ""
    at.indent = 0 # Changed only for sentinels.
    at.rootSeen = false
    
    # Stacked ivars...
    at.endSentinelStack = [endLeo] # We have already handled the @+leo sentinel.
    at.out = [] ; at.outStack = []
    at.t = v.t ; at.tStack = []
    
    if 0: # Useful for debugging.
      if hasattr(v,"tnodeList"):
        trace("len(v.tnodeList)",len(v.tnodeList),v)
      else:
        trace("no tnodeList",v)
    #@nonl
    #@-node:<< init ivars for scanText4 >>
    #@nl
    while at.errors == 0 and not at.done:
      s = at.readLine(file)
      if len(s) == 0: break
      kind = at.sentinelKind(s)
      # trace(at.sentinelName(kind),`s`)
      if kind == noSentinel:
        i = 0
      else:
        i = at.skipSentinelStart(s,0)
      func = at.dispatch_dict[kind]
      func(s,i)
  
    if at.errors == 0 and not at.done:
      #@    << report unexpected end of text >>
      #@+node:<< report unexpected end of text >>
      assert(at.endSentinelStack)
      
      at.readError(
        "Unexpected end of file. Expecting %s sentinel" %
        at.sentinelName(at.endSentinelStack[-1]))
      #@nonl
      #@-node:<< report unexpected end of text >>
      #@nl
  
    return at.lastLines
  #@nonl
  #@-node:scanText4 & allies
  #@+node:readNormalLine
  def readNormalLine (self,s,i):
  
    at = self
    
    if at.inCode:
      if not at.raw:
        s = removeLeadingWhitespace(s,at.indent,at.tab_width)
      at.out.append(s)
    else:
      #@    << Skip the leading stuff >>
      #@+node:<< Skip the leading stuff >>
      if len(at.endSentinelComment) == 0:
        # Skip the single comment delim and a blank.
        i = skip_ws(s,0)
        if match(s,i,at.startSentinelComment):
          i += len(at.startSentinelComment)
          if match(s,i," "): i += 1
      else:
        i = at.skipIndent(s,0,at.indent)
      
      #@-node:<< Skip the leading stuff >>
      #@nl
      #@    << Append s to docOut >>
      #@+node:<< Append s to docOut >>
      line = s[i:-1] # remove newline for rstrip.
      
      if line == line.rstrip():
        # no trailing whitespace: the newline is real.
        at.docOut.append(line + '\n')
      else:
        # trailing whitespace: the newline is fake.
        at.docOut.append(line)
      #@nonl
      #@-node:<< Append s to docOut >>
      #@nl
  #@nonl
  #@-node:readNormalLine
  #@+node:readStartAt & readStartDoc
  def readStartAt (self,s,i):
    """Read an @+at sentinel."""
    at = self ; assert(match(s,i,"+at"))
    if 0:# new code: append whatever follows the sentinel.
      i += 3 ; j = self.skipToEndSentinel(s,i) ; follow = s[i:j]
      at.out.append('@' + follow) ; at.docOut = []
    else:
      i += 3 ; j = skip_ws(s,i) ; ws = s[i:j]
      at.docOut = ['@' + ws + '\n'] # This newline may be removed by a following @nonl
    at.inCode = false
    at.endSentinelStack.append(endAt)
    
  def readStartDoc (self,s,i):
    """Read an @+doc sentinel."""
    at = self ; assert(match(s,i,"+doc"))
    if 0: # new code: append whatever follows the sentinel.
      i += 4 ; j = self.skipToEndSentinel(s,i) ; follow = s[i:j]
      at.out.append('@' + follow) ; at.docOut = []
    else:
      i += 4 ; j = skip_ws(s,i) ; ws = s[i:j]
      at.docOut = ["@doc" + ws + '\n'] # This newline may be removed by a following @nonl
    at.inCode = false
    at.endSentinelStack.append(endDoc)
    
  def skipToEndSentinel(self,s,i):
    end = self.endSentinelComment
    if end:
      j = s.find(end,i)
      if j == -1:
        return skip_to_end_of_line(s,i)
      else:
        return j
    else:
      return skip_to_end_of_line(s,i)
  #@nonl
  #@-node:readStartAt & readStartDoc
  #@+node:readStartLeo
  def readStartLeo (self,s,i):
    
    """Read an unexpected @+leo sentinel."""
  
    at = self
    assert(match(s,i,"+leo"))
    at.readError("Ignoring unexpected @+leo sentinel")
  #@nonl
  #@-node:readStartLeo
  #@+node:readStartNode
  def readStartNode (self,s,i):
    
    """Read an @node sentinel."""
    
    at = self ; assert(match(s,i,"+node:"))
    i += 6
    
    #@  << Set headline, undoing the CWEB hack >>
    #@+node:<< Set headline, undoing the CWEB hack >>
    # Set headline to the rest of the line.
    # Don't strip leading whitespace."
    
    if len(at.endSentinelComment) == 0:
      headline = s[i:-1].rstrip()
    else:
      k = s.rfind(at.endSentinelComment,i)
      headline = s[i:k].rstrip() # works if k == -1
    
    # Undo the CWEB hack: undouble @ signs if the opening comment delim ends in '@'.
    if at.startSentinelComment[-1:] == '@':
      headline = headline.replace('@@','@')
    #@nonl
    #@-node:<< Set headline, undoing the CWEB hack >>
    #@nl
    if not at.root_seen:
      at.root_seen = true
      if not at.importing:
        #@      << Check the filename in the sentinel >>
        #@+node:<< Check the filename in the sentinel >>
        h = headline.strip()
        
        if h[:5] == "@file":
          i,junk,junk = scanAtFileOptions(h)
          fileName = string.strip(h[i:])
          if fileName != at.targetFileName:
            at.readError("File name in @node sentinel does not match file's name")
        elif h[:8] == "@rawfile":
          fileName = string.strip(h[8:])
          if fileName != at.targetFileName:
            at.readError("File name in @node sentinel does not match file's name")
        else:
          at.readError("Missing @file in root @node sentinel")
        #@nonl
        #@-node:<< Check the filename in the sentinel >>
        #@nl
  
    i,newIndent = skip_leading_ws_with_indent(s,0,at.tab_width)
    at.indentStack.append(at.indent) ; at.indent = newIndent
    
    at.outStack.append(at.out) ; at.out = []
    at.tStack.append(at.t) ; at.t = at.findChild(headline)
    
    at.endSentinelStack.append(endNode)
  #@nonl
  #@-node:readStartNode
  #@+node:readStartOthers
  def readStartOthers (self,s,i):
    
    """Read an @+others sentinel."""
  
    at = self
    j = skip_ws(s,i)
    leadingWs = s[i:j]
    if leadingWs:
      assert(match(s,j,"@+others"))
    else:
      assert(match(s,j,"+others"))
  
    # Make sure that the generated at-others is properly indented.
    at.out.append(leadingWs + "@others\n")
    
    at.endSentinelStack.append(endOthers)
  #@nonl
  #@-node:readStartOthers
  #@+node:readEndAt & readEndDoc
  def readEndAt (self,s,i):
    
    """Read an @-at sentinel."""
  
    at = self
    at.readLastDocLine("@")
    at.popSentinelStack(endAt)
    at.inCode = true
      
  def readEndDoc (self,s,i):
    
    """Read an @-doc sentinel."""
  
    at = self
    at.readLastDocLine("@doc")
    at.popSentinelStack(endDoc)
    at.inCode = true
  #@nonl
  #@-node:readEndAt & readEndDoc
  #@+node:readEndLeo
  def readEndLeo (self,s,i):
    
    """Read an @-leo sentinel."""
    
    at = self
  
    # Ignore everything after @-leo.
    # Such lines were presumably written by @last.
    while 1:
      s = at.readLine(at.file)
      if len(s) == 0: break
      at.lastLines.append(s) # Capture all trailing lines, even if empty.
  
    at.done = true
  #@nonl
  #@-node:readEndLeo
  #@+node:readEndNode
  def readEndNode (self,s,i):
    
    """Handle end-of-node processing for @-others and @-ref sentinels."""
  
    at = self
    
    # End raw mode.
    at.raw = false
    
    # Set the temporary body text.
    s = ''.join(at.out)
    s = toUnicode(s,app.tkEncoding) # 9/28/03
  
    if at.importing:
      at.t.bodyString = s
    else:
      at.t.tempBodyString = s
        
    # Indicate that the tnode has been set in the derived file.
    at.t.setVisited()
  
    # End the previous node sentinel.
    at.indent = at.indentStack.pop()
    at.out = at.outStack.pop()
    at.t = at.tStack.pop()
  
    at.popSentinelStack(endNode)
  #@nonl
  #@-node:readEndNode
  #@+node:readEndOthers
  def readEndOthers (self,s,i):
    
    """Read an @-others sentinel."""
    
    at = self
    at.popSentinelStack(endOthers)
  #@nonl
  #@-node:readEndOthers
  #@+node:readLastDocLine
  def readLastDocLine (self,tag):
    
    """Read the @c line that terminates the doc part.
    tag is @doc or @."""
    
    at = self
    end = at.endSentinelComment
    start = at.startSentinelComment
    s = ''.join(at.docOut)
    
    if 0: # new code.
      #@    << new code >>
      #@+node:<< new code >>
      if end:
        # Remove opening block delim.
        if match(s,0,start):
          s = s[len(start):]
        else:
          at.readError("Missing open block comment")
          trace(s)
          return
          
        # Remove trailing newline.
        if s[-1] == '\n':
          s = s[:-1]
      
        # Remove closing block delim.
        if s[-len(end):] == end:
          s = s[:-len(end)]
        else:
          at.readError("Missing close block comment")
          return
      
      at.out.append(s) # The tag has already been removed.
      at.docOut = []
      #@nonl
      #@-node:<< new code >>
      #@nl
    else:
      #@    << old code >>
      #@+node:<< old code >>
      # Remove the @doc or @space.  We'll add it back at the end.
      if match(s,0,tag):
        s = s[len(tag):]
      else:
        at.readError("Missing start of doc part")
        return
      
      if end:
        # Remove opening block delim.
        if match(s,0,start):
          s = s[len(start):]
        else:
          at.readError("Missing open block comment")
          trace(s)
          return
          
        # Remove trailing newline.
        if s[-1] == '\n':
          s = s[:-1]
      
        # Remove closing block delim.
        if s[-len(end):] == end:
          s = s[:-len(end)]
        else:
          at.readError("Missing close block comment")
          return
      
      at.out.append(tag + s)
      at.docOut = []
      #@nonl
      #@-node:<< old code >>
      #@nl
  #@nonl
  #@-node:readLastDocLine
  #@+node:ignoreOldSentinel
  def  ignoreOldSentinel (self,s,i):
    
    """Ignore an 3.x sentinel."""
    
    es("Ignoring 3.x sentinel: " + s.strip(), color="blue")
  #@nonl
  #@-node:ignoreOldSentinel
  #@+node:readAfterRef
  def  readAfterRef (self,s,i):
    
    """Read an @afterref sentinel."""
    
    at = self
    assert(match(s,i,"afterref"))
    
    # Append the next line to the text.
    s = at.readLine(at.file)
    at.out.append(s)
  #@nonl
  #@-node:readAfterRef
  #@+node:readComment
  def readComment (self,s,i):
    
    """Read an @comment sentinel."""
  
    assert(match(s,i,"comment"))
  
    # Just ignore the comment line!
  #@-node:readComment
  #@+node:readDelims
  def readDelims (self,s,i):
    
    """Read an @delims sentinel."""
    
    at = self
    assert(match(s,i-1,"@delims"));
  
    # Skip the keyword and whitespace.
    i0 = i-1
    i = skip_ws(s,i-1+7)
      
    # Get the first delim.
    j = i
    while i < len(s) and not is_ws(s[i]) and not is_nl(s,i):
      i += 1
    
    if j < i:
      at.startSentinelComment = s[j:i]
      # print "delim1:", at.startSentinelComment
    
      # Get the optional second delim.
      j = i = skip_ws(s,i)
      while i < len(s) and not is_ws(s[i]) and not is_nl(s,i):
        i += 1
      end = choose(j<i,s[j:i],"")
      i2 = skip_ws(s,i)
      if end == at.endSentinelComment and (i2 >= len(s) or is_nl(s,i2)):
        at.endSentinelComment = "" # Not really two params.
        line = s[i0:j]
        line = line.rstrip()
        at.out.append(line+'\n')
      else:
        at.endSentinelComment = end
        # print "delim2:",end
        line = s[i0:i]
        line = line.rstrip()
        at.out.append(line+'\n')
    else:
      at.readError("Bad @delims")
      # Append the bad @delims line to the body text.
      at.out.append("@delims")
  #@nonl
  #@-node:readDelims
  #@+node:readDirective
  def readDirective (self,s,i):
    
    """Read an @@sentinel."""
    
    at = self
    assert(match(s,i,"@")) # The first '@' has already been eaten.
    
    if match_word(s,i,"@raw"):
      at.raw = true
    elif match_word(s,i,"@end_raw"):
      at.raw = false
    
    e = at.endSentinelComment
    s2 = s[i:]
    if len(e) > 0:
      k = s.rfind(e,i)
      if k != -1:
        s2 = s[i:k] + '\n'
      
    start = at.startSentinelComment
    if start and len(start) > 0 and start[-1] == '@':
      s2 = s2.replace('@@','@')
  
    at.out.append(s2)
  #@nonl
  #@-node:readDirective
  #@+node:readNl
  def readNl (self,s,i):
    
    """Handle an @nonl sentinel."""
    
    at = self
    assert(match(s,i,"nl"))
    
    if at.inCode:
      at.out.append('\n')
    else:
      at.docOut.append('\n')
  #@nonl
  #@-node:readNl
  #@+node:readNonl
  def readNonl (self,s,i):
    
    """Handle an @nonl sentinel."""
    
    at = self
    assert(match(s,i,"nonl"))
    
    if at.inCode:
      s = ''.join(at.out)
      if s and s[-1] == '\n':
        at.out = [s[:-1]]
      else:
        trace("out:",`s`)
        at.readError("unexpected @nonl directive in code part")  
    else:
      s = ''.join(at.pending)
      if s:
        if s and s[-1] == '\n':
          at.pending = [s[:-1]]
        else:
          trace("docOut:",`s`)
          at.readError("unexpected @nonl directive in pending doc part")
      else:
        s = ''.join(at.docOut)
        if s and s[-1] == '\n':
          at.docOut = [s[:-1]]
        else:
          trace("docOut:",`s`)
          at.readError("unexpected @nonl directive in doc part")
  #@nonl
  #@-node:readNonl
  #@+node:readRef
  #@+at 
  #@nonl
  # The sentinel contains an @ followed by a section name in angle 
  # brackets.  This code is different from the code for the @@ sentinel: the 
  # expansion of the reference does not include a trailing newline.
  #@-at
  #@@c
  
  def readRef (self,s,i):
    
    """Handle an @<< sentinel."""
    
    at = self
    j = skip_ws(s,i)
    assert(match(s,j,"<<"))
    
    if len(at.endSentinelComment) == 0:
      line = s[i:-1] # No trailing newline
    else:
      k = s.find(at.endSentinelComment,i)
      line = s[i:k] # No trailing newline, whatever k is.
        
    # Undo the cweb hack.
    start = at.startSentinelComment
    if start and len(start) > 0 and start[-1] == '@':
      line = line.replace('@@','@')
  
    at.out.append(line)
  #@nonl
  #@-node:readRef
  #@+node:readVerbatim
  def readVerbatim (self,s,i):
    
    """Read an @verbatim sentinel."""
    
    at = self
    assert(match(s,i,"verbatim"))
    
    # Append the next line to the text.
    s = at.readLine(at.file) 
    i = at.skipIndent(s,0,at.indent)
    at.out.append(s[i:])
  #@nonl
  #@-node:readVerbatim
  #@+node:badEndSentinel, push/popSentinelStack
  def badEndSentinel (self,expectedKind):
    
    """Handle a mismatched ending sentinel."""
  
    at = self
    assert(at.endSentinelStack)
    at.readError("Ignoring %s sentinel.  Expecting %s" %
      (at.sentinelName(at.endSentinelStack[-1]),
       at.sentinelName(expectedKind)))
       
  def popSentinelStack (self,expectedKind):
    
    """Pop an entry from endSentinelStack and check it."""
    
    at = self
    if at.endSentinelStack and at.endSentinelStack[-1] == expectedKind:
      at.endSentinelStack.pop()
    else:
      at.badEndSentinel(expectedKind)
  #@nonl
  #@-node:badEndSentinel, push/popSentinelStack
  #@+node:nodeSentinelText
  def nodeSentinelText(self,v):
    
    """Return the text of a @+node or @-node sentinel for v."""
    
    at = self ; h = v.headString()
    #@  << remove comment delims from h if necessary >>
    #@+node:<< remove comment delims from h if necessary >>
    #@+at 
    #@nonl
    # Bug fix 1/24/03:
    # 
    # If the present @language/@comment settings do not specify a 
    # single-line comment we remove all block comment delims from h.  This 
    # prevents headline text from interfering with the parsing of node 
    # sentinels.
    #@-at
    #@@c
    
    start = at.startSentinelComment
    end = at.endSentinelComment
    
    if end and len(end) > 0:
      h = h.replace(start,"")
      h = h.replace(end,"")
    #@nonl
    #@-node:<< remove comment delims from h if necessary >>
    #@nl
    return h
  #@nonl
  #@-node:nodeSentinelText
  #@+node:putLeadInSentinel
  def putLeadInSentinel (self,s,i,j,delta):
    
    """Generate @nonl sentinels as needed to ensure a newline before a group of sentinels.
    
    Set at.leadingWs as needed for @+others and @+<< sentinels.
  
    i points at the start of a line.
    j points at @others or a section reference.
    delta is the change in at.indent that is about to happen and hasn't happened yet."""
  
    at = self
    at.leadingWs = "" # Set the default.
    if i == j:
      return # The @others or ref starts a line.
  
    k = skip_ws(s,i)
    if j == k:
      # Only whitespace before the @others or ref.
      at.leadingWs = s[i:j] # Remember the leading whitespace, including its spelling.
    else:
      at.os(s[i:j]) ; at.onl_sent() # 10/21/03
      at.indent += delta # Align the @nonl with the following line.
      at.putSentinel("@nonl")
      at.indent -= delta # Let the caller set at.indent permanently.
  #@nonl
  #@-node:putLeadInSentinel
  #@+node:putOpenLeoSentinel
  def putOpenLeoSentinel(self,s):
    
    """Write @+leo sentinel."""
  
    at = self
    
    if not at.sentinels:
      return # Handle @nosentinelsfile.
  
    encoding = at.encoding.lower()
    if encoding != "utf-8":
      s = s + "-encoding=%s." % (encoding)
    
    at.putSentinel(s)
  #@nonl
  #@-node:putOpenLeoSentinel
  #@+node:putOpenNodeSentinel (sets tnodeList)
  def putOpenNodeSentinel(self,v):
    
    """Write @+node sentinel for v."""
    
    at = self
  
    if v.isAtFileNode() and v != at.root:
      at.writeError("@file not valid in: " + v.headString())
      return
    
    s = at.nodeSentinelText(v)
    at.putSentinel("@+node:" + s)
  
    # Append the n'th tnode to the root's tnode list.
  
    # trace("%3d %3d" % (len(at.root.tnodeList),v.t.fileIndex),v)
    at.root.tnodeList.append(v.t)
  #@nonl
  #@-node:putOpenNodeSentinel (sets tnodeList)
  #@+node:putSentinel (applies cweb hack)
  # This method outputs all sentinels.
  
  def putSentinel(self,s):
  
    "Write a sentinel whose text is s, applying the CWEB hack if needed."
    
    at = self
  
    if not at.sentinels:
      return # Handle @file-nosent
  
    at.putIndent(at.indent)
    at.os(at.startSentinelComment)
    #@  << apply the cweb hack to s >>
    #@+node:<< apply the cweb hack to s >>
    #@+at 
    #@nonl
    # The cweb hack:
    # 
    # If the opening comment delim ends in '@', double all '@' signs 
    # except the first, which is "doubled" by the trailing '@' in the 
    # opening comment delimiter.
    #@-at
    #@@c
    
    start = at.startSentinelComment
    if start and start[-1] == '@':
      assert(s and s[0]=='@')
      s = s.replace('@','@@')[1:]
    #@nonl
    #@-node:<< apply the cweb hack to s >>
    #@nl
    at.os(s)
    if at.endSentinelComment:
      at.os(at.endSentinelComment)
    at.onl()
  #@nonl
  #@-node:putSentinel (applies cweb hack)
  #@+node:skipSentinelStart
  def skipSentinelStart(self,s,i):
    
    """Skip the start of a sentinel."""
  
    start = self.startSentinelComment
    assert(start and len(start)>0)
  
    i = skip_ws(s,i)
    assert(match(s,i,start))
    i += len(start)
  
    # 7/8/02: Support for REM hack
    i = skip_ws(s,i)
    assert(i < len(s) and s[i] == '@')
    return i + 1
  #@-node:skipSentinelStart
  #@+node:sentinelKind
  def sentinelKind(self,s):
    
    """Return the kind of sentinel at s."""
    
    at = self
  
    i = skip_ws(s,0)
    if match(s,i,at.startSentinelComment): 
      i += len(at.startSentinelComment)
    else:
      return noSentinel
  
    # Locally undo cweb hack here
    start = at.startSentinelComment
    if start and len(start) > 0 and start[-1] == '@':
      s = s[:i] + string.replace(s[i:],'@@','@')
      
    # 4.0: Look ahead for @[ws]@others and @[ws]<<
    if match(s,i,"@"):
      j = skip_ws(s,i+1)
      if j > i+1:
        # trace(`ws`,`s`)
        if match(s,j,"@+others"):
          return startOthers
        elif match(s,j,"<<"):
          return startRef
        else:
          # No other sentinels allow whitespace following the '@'
          return noSentinel
  
    # Do not skip whitespace here!
    if match(s,i,"@<<"): return startRef
    if match(s,i,"@@"):   return startDirective
    if not match(s,i,'@'): return noSentinel
    j = i # start of lookup
    i += 1 # skip the at sign.
    if match(s,i,'+') or match(s,i,'-'):
      i += 1
    i = skip_c_id(s,i)
    key = s[j:i]
    if len(key) > 0 and sentinelDict.has_key(key):
      return sentinelDict[key]
    else:
      return noSentinel
  #@nonl
  #@-node:sentinelKind
  #@+node:new_df.closeWriteFile
  # 4.0: Don't use newline-pending logic.
  
  def closeWriteFile (self):
    
    at = self
    if at.outputFile:
      at.outputFile.flush()
      at.outputFile.close()
      at.outputFile = None
  #@nonl
  #@-node:new_df.closeWriteFile
  #@+node:new_df.write (inits root.tnodeList)
  # This is the entry point to the write code.  root should be an @file vnode.
  
  def write(self,root,nosentinels=false,scriptFile=None):
    
    """Write a 4.x derived file."""
    
    at = self ; c = at.c
    if at.trace: trace("new_df",root)
  
    at.sentinels = not nosentinels
    #@  << initialize >>
    #@+node:<< initialize >>
    at.errors = 0
    c.setIvarsFromPrefs()
    at.root = root
    at.root.tnodeList = []
    at.raw = false
    c.endEditing() # Capture the current headline.
    #@nonl
    #@-node:<< initialize >>
    #@nl
    try:
      #@    << open the file; return on error >>
      #@+node:<< open the file; return on error >>
      if scriptFile:
        at.targetFileName = "<script>"
      if nosentinels:
        at.targetFileName = root.atNoSentinelsFileNodeName()
      else:
        at.targetFileName = root.atFileNodeName()
      
      if scriptFile:
        ok = true
        at.outputFileName = "<script>"
        at.outputFile = scriptFile
      else:
        ok = at.openWriteFile(root)
        
      if not ok:
        return
      #@-node:<< open the file; return on error >>
      #@nl
      root.clearVisitedInTree()
      #@    << write then entire @file tree >>
      #@+node:<< write then entire @file tree >> (4.x)
      # unvisited nodes will be orphans, except in cweb trees.
      root.clearVisitedInTree()
      
      #@<< put all @first lines in root >>
      #@+node:<< put all @first lines in root >>
      #@+at 
      #@nonl
      # Write any @first lines.  These lines are also converted to 
      # @verbatim lines, so the read logic simply ignores lines 
      # preceding the @+leo sentinel.
      #@-at
      #@@c
      
      s = root.t.bodyString
      tag = "@first"
      i = 0
      while match(s,i,tag):
        i += len(tag)
        i = skip_ws(s,i)
        j = i
        i = skip_to_end_of_line(s,i)
        # Write @first line, whether empty or not
        line = s[j:i]
        self.os(line) ; self.onl()
        i = skip_nl(s,i)
      #@nonl
      #@-node:<< put all @first lines in root >>
      #@nl
      
      # Put the main part of the file.
      at.putOpenLeoSentinel("@+leo-ver=4")
      at.putInitialComment()
      at.putBody(root)
      at.putSentinel("@-leo")
      root.setVisited()
      
      #@<< put all @last lines in root >>
      #@+node:<< put all @last lines in root >> (4.x)
      #@+at 
      #@nonl
      # Write any @last lines.  These lines are also converted to 
      # @verbatim lines, so the read logic simply ignores lines 
      # following the @-leo sentinel.
      #@-at
      #@@c
      
      tag = "@last"
      lines = string.split(root.t.bodyString,'\n')
      n = len(lines) ; j = k = n - 1
      # Don't write an empty last line.
      if j >= 0 and len(lines[j])==0:
        j = k = n - 2
      # Scan backwards for @last directives.
      while j >= 0:
        line = lines[j]
        if match(line,0,tag): j -= 1
        else: break
      # Write the @last lines.
      for line in lines[j+1:k+1]:
        i = len(tag) ; i = skip_ws(line,i)
        self.os(line[i:]) ; self.onl()
      #@nonl
      #@-node:<< put all @last lines in root >> (4.x)
      #@nl
      
      
      #@-node:<< write then entire @file tree >> (4.x)
      #@nl
      if not scriptFile:
        at.closeWriteFile()
        if not nosentinels:
          #@        << warn about @ignored and orphans >>
          #@+node:<< Warn about @ignored and orphans  >>
          # 10/26/02: Always warn, even when language=="cweb"
          
          next = root.nodeAfterTree()
          v = root
          while v and v != next:
            if not v.isVisited():
              at.writeError("Orphan node:  " + v.headString())
            if v.isAtIgnoreNode():
              at.writeError("@ignore node: " + v.headString())
            v = v.threadNext()
          #@-node:<< Warn about @ignored and orphans  >>
          #@nl
        #@      << finish writing >>
        #@+node:<< finish writing >>
        #@+at 
        #@nonl
        # We set the orphan and dirty flags if there are problems 
        # writing the file to force Commands::write_LEO_file to write 
        # the tree to the .leo file.
        #@-at
        #@@c
        
        if at.errors > 0 or at.root.isOrphan():
          root.setOrphan()
          root.setDirty() # 2/9/02: make _sure_ we try to rewrite this file.
          os.remove(at.outputFileName) # Delete the temp file.
          es("Not written: " + at.outputFileName)
        else:
          root.clearOrphan()
          root.clearDirty()
          at.replaceTargetFileIfDifferent()
        #@nonl
        #@-node:<< finish writing >>
        #@nl
    except:
      if scriptFile:
        es("exception preprocessing script",color="blue")
        es_exception(full=false)
        scriptFile.clear()
      else:
        at.handleWriteException()
  #@nonl
  #@-node:new_df.write (inits root.tnodeList)
  #@+node:new_df.rawWrite
  def rawWrite(self,root):
  
    at = self
    if at.trace: trace("new_df",root)
  
    c = at.c ; at.root = root
    at.errors = 0
    at.root.tnodeList = [] # 9/26/03: after beta 1 release.
    at.sentinels = true # 10/1/03
    c.endEditing() # Capture the current headline.
    try:
      at.targetFileName = root.atRawFileNodeName()
      ok = at.openWriteFile(root)
      if not ok: return
      #@    << write root's tree >>
      #@+node:<< write root's tree >>
      #@<< put all @first lines in root >>
      #@+node:<< put all @first lines in root >>
      #@+at 
      #@nonl
      # Write any @first lines.  These lines are also converted to 
      # @verbatim lines, so the read logic simply ignores lines 
      # preceding the @+leo sentinel.
      #@-at
      #@@c
      
      s = root.t.bodyString
      tag = "@first"
      i = 0
      while match(s,i,tag):
        i += len(tag)
        i = skip_ws(s,i)
        j = i
        i = skip_to_end_of_line(s,i)
        # Write @first line, whether empty or not
        line = s[j:i]
        at.putBuffered(line) ; at.onl()
        i = skip_nl(s,i)
      #@nonl
      #@-node:<< put all @first lines in root >>
      #@nl
      at.putOpenLeoSentinel("@+leo-ver=4")
      #@<< put optional @comment sentinel lines >>
      #@+node:<< put optional @comment sentinel lines >>
      s2 = app.config.output_initial_comment
      if s2:
        lines = string.split(s2,"\\n")
        for line in lines:
          line = line.replace("@date",time.asctime())
          if len(line)> 0:
            at.putSentinel("@comment " + line)
      #@-node:<< put optional @comment sentinel lines >>
      #@nl
      
      next = root.nodeAfterTree()
      v = root
      while v and v != next:
        #@  << Write v's node >>
        #@+node:<< Write v's node >>
        at.putOpenNodeSentinel(v)
        
        s = v.bodyString()
        if s and len(s) > 0:
          s = toEncodedString(s,at.encoding,reportErrors=true) # 3/7/03
          at.outputStringWithLineEndings(s)
          
        # Put an @nonl sentinel if s does not end in a newline.
        if s and s[-1] != '\n':
          at.onl_sent() ; at.putSentinel("@nonl")
        
        at.putCloseNodeSentinel(v)
        #@-node:<< Write v's node >>
        #@nl
        v = v.threadNext()
      
      at.putSentinel("@-leo")
      #@<< put all @last lines in root >>
      #@+node:<< put all @last lines in root >>
      #@+at 
      #@nonl
      # Write any @last lines.  These lines are also converted to 
      # @verbatim lines, so the read logic simply ignores lines 
      # following the @-leo sentinel.
      #@-at
      #@@c
      
      tag = "@last"
      lines = string.split(root.t.bodyString,'\n')
      n = len(lines) ; j = k = n - 1
      # Don't write an empty last line.
      if j >= 0 and len(lines[j])==0:
        j = k = n - 2
      # Scan backwards for @last directives.
      while j >= 0:
        line = lines[j]
        if match(line,0,tag): j -= 1
        else: break
      # Write the @last lines.
      for line in lines[j+1:k+1]:
        i = len(tag) ; i = skip_ws(line,i)
        at.putBuffered(line[i:]) ; at.onl()
      #@nonl
      #@-node:<< put all @last lines in root >>
      #@nl
      #@nonl
      #@-node:<< write root's tree >>
      #@nl
      at.closeWriteFile()
      at.replaceTargetFileIfDifferent()
      root.clearOrphan() ; root.clearDirty()
    except:
      at.handleWriteException(root)
  #@nonl
  #@-node:new_df.rawWrite
  #@+node:putBody (4.x)
  def putBody(self,v):
    
    """ Generate the body enclosed in sentinel lines."""
  
    at = self ; s = v.bodyString()
    
    v.setVisited() # Mark the node for the orphans check.
    if not s: return
  
    inCode = true
    
    # Make _sure_ all lines end in a newline (11/20/03: except in nosentinel mode).
    # If we add a trailing newline, we'll generate an @nonl sentinel below.
    trailingNewlineFlag = s and s[-1] == '\n'
    if at.sentinels and not trailingNewlineFlag:
      s = s + '\n'
  
    at.putOpenNodeSentinel(v)
    i = 0
    while i < len(s):
      next_i = skip_line(s,i)
      assert(next_i > i)
      kind = at.directiveKind(s,i)
      #@    << handle line at s[i] >>
      #@+node:<< handle line at s[i]  >> (4.x)
      if kind == noDirective:
        if inCode:
          hasRef,n1,n2 = at.findSectionName(s,i)
          if hasRef and not at.raw:
            at.putRefLine(s,i,n1,n2,v)
          else:
            at.putCodeLine(s,i)
        else:
          at.putDocLine(s,i)
      elif kind in (docDirective,atDirective):
        assert(not at.pending)
        at.putStartDocLine(s,i,kind)
        inCode = false
      elif kind in (cDirective,codeDirective):
        # Only @c and @code end a doc part.
        if not inCode:
          at.putEndDocLine() 
        at.putDirective(s,i)
        inCode = true
      elif kind == othersDirective:
        if inCode: at.putAtOthersLine(s,i,v)
        else: at.putDocLine(s,i) # 12/7/03
      elif kind == rawDirective:
        at.raw = true
        at.putSentinel("@@raw")
      elif kind == endRawDirective:
        at.raw = false
        at.putSentinel("@@end_raw")
        i = skip_line(s,i)
      elif kind == miscDirective:
        at.putDirective(s,i)
      else:
        assert(0) # Unknown directive.
      #@nonl
      #@-node:<< handle line at s[i]  >> (4.x)
      #@nl
      i = next_i
    if not inCode:
      at.putEndDocLine()
    if at.sentinels and not trailingNewlineFlag:
      at.putSentinel("@nonl")
    at.putCloseNodeSentinel(v)
  #@nonl
  #@-node:putBody (4.x)
  #@+node:inAtOthers
  def inAtOthers(self,v):
    
    """Returns true if v should be included in the expansion of the at-others directive
    
    in the body text of v's parent."""
  
    # Return false if this has been expanded previously.
    if  v.isVisited():
      # trace("previously visited",v)
      return false
    
    # Return false if this is a definition node.
    h = v.headString() ; i = skip_ws(h,0)
    isSection,junk = self.isSectionName(h,i)
    if isSection:
      # trace("is section",v)
      return false
  
    # Return false if v's body contains an @ignore directive.
    if v.isAtIgnoreNode():
      # trace("is @ignore",v)
      return false
    else:
      # trace("ok",v)
      return true
  #@nonl
  #@-node:inAtOthers
  #@+node:putAtOthersChild
  def putAtOthersChild(self,v):
  
    v.setVisited() # Make sure v is never expanded again.
    self.putBody(v) # Insert the expansion of v.
  
    # Insert expansions of all children.
    child = v.firstChild()
    while child:
      if self.inAtOthers( child ):
        self.putAtOthersChild( child )
      child = child.next()
  #@-node:putAtOthersChild
  #@+node:putAtOthersLine
  def putAtOthersLine (self,s,i,v):
    
    """Put the expansion of @others."""
    
    at = self
    j,delta = skip_leading_ws_with_indent(s,i,at.tab_width)
    at.putLeadInSentinel(s,i,j,delta)
  
    at.indent += delta
    if at.leadingWs:
      at.putSentinel("@" + at.leadingWs + "@+others")
    else:
      at.putSentinel("@+others")
    
    child = v.firstChild()
    while child:
      if at.inAtOthers(child):
        at.putAtOthersChild(child)
      child = child.next()
  
    at.putSentinel("@-others")
    at.indent -= delta
  #@nonl
  #@-node:putAtOthersLine
  #@+node:putCodeLine
  def putCodeLine (self,s,i):
    
    """Put a normal code line."""
    
    at = self
    
    # Put @verbatim sentinel if required.
    k = skip_ws(s,i)
    if match(s,k,self.startSentinelComment + '@'):
      self.putSentinel("@verbatim")
  
    j = skip_line(s,i)
    if not at.raw:
      at.putIndent(at.indent)
    line = s[i:j]
    # at.os(line)
    if line[-1:]=="\n": # 12/2/03: emakital
      at.os(line[:-1])
      at.onl()
    else:
      at.os(line)
  #@nonl
  #@-node:putCodeLine
  #@+node:putRefLine (new) & allies
  def putRefLine(self,s,i,n1,n2,v):
    
    """Put a line containing one or more references."""
    
    at = self
    
    # Compute delta only once.
    delta = self.putRefAt(s,i,n1,n2,v,delta=None)
    if delta is None: return # 11/23/03
    
    while 1:
      i = n2 + 2
      hasRef,n1,n2 = at.findSectionName(s,i)
      if hasRef:
        self.putAfterMiddleRef(s,i,n1,delta)
        self.putRefAt(s,n1,n1,n2,v,delta)
      else:
        break
    
    self.putAfterLastRef(s,i,delta)
  #@nonl
  #@-node:putRefLine (new) & allies
  #@+node:PutRefAt
  def putRefAt (self,s,i,n1,n2,v,delta):
    
    """Put a reference at s[n1:n2+2] from v."""
    
    at = self ; name = s[n1:n2+2]
  
    ref = findReference(name,v)
    if not ref:
      at.writeError(
        "undefined section: %s\n\treferenced from: %s" %
        ( name,v.headString()))
      return None
    
    # Expand the ref.
    if not delta:
      junk,delta = skip_leading_ws_with_indent(s,i,at.tab_width)
    at.putLeadInSentinel(s,i,n1,delta)
  
    at.indent += delta
    if at.leadingWs:
      at.putSentinel("@" + at.leadingWs + name)
    else:
      at.putSentinel("@" + name)
    at.putBody(ref)
    at.indent -= delta
    
    return delta
  #@nonl
  #@-node:PutRefAt
  #@+node:putAfterLastRef
  def putAfterLastRef (self,s,start,delta):
    
    """Handle whatever follows the last ref of a line."""
    
    at = self
    
    j = skip_ws(s,start)
    
    if j < len(s) and s[j] != '\n':
      end = skip_line(s,start)
      after = s[start:end] # Ends with a newline only if the line did.
      # Temporarily readjust delta to make @afterref look better.
      at.indent += delta
      at.putSentinel("@afterref")
      at.os(after)
      if at.sentinels and after and after[-1] != '\n':
        at.onl() # Add a newline if the line didn't end with one.
      at.indent -= delta
    else:
      # Temporarily readjust delta to make @nl look better.
      at.indent += delta
      at.putSentinel("@nl")
      at.indent -= delta
  #@nonl
  #@-node:putAfterLastRef
  #@+node:putAfterMiddleef
  def putAfterMiddleRef (self,s,start,end,delta):
    
    """Handle whatever follows a ref that is not the last ref of a line."""
    
    at = self
    
    if start < end:
      after = s[start:end]
      at.indent += delta
      at.putSentinel("@afterref")
      at.os(after) ; at.onl_sent() # Not a real newline.
      at.putSentinel("@nonl")
      at.indent -= delta
  #@nonl
  #@-node:putAfterMiddleef
  #@+node:putBlankDocLine
  def putBlankDocLine (self):
    
    at = self
    
    at.putPending(split=false)
  
    if not at.endSentinelComment:
      at.putIndent(at.indent)
      at.os(at.startSentinelComment) ; at.oblank()
  
    at.onl()
  #@nonl
  #@-node:putBlankDocLine
  #@+node:putStartDocLine
  def putStartDocLine (self,s,i,kind):
    
    """Write the start of a doc part."""
    
    at = self ; at.docKind = kind
    
    sentinel = choose(kind == docDirective,"@+doc","@+at")
    directive = choose(kind == docDirective,"@doc","@")
    
    if 0: # New code: put whatever follows the directive in the sentinel
      # Skip past the directive.
      i += len(directive)
      j = skip_to_end_of_line(s,i)
      follow = s[i:j]
    
      # Put the opening @+doc or @-doc sentinel, including whatever follows the directive.
      at.putSentinel(sentinel + follow)
  
      # Put the opening comment if we are using block comments.
      if at.endSentinelComment:
        at.putIndent(at.indent)
        at.os(at.startSentinelComment) ; at.onl()
    else: # old code.
      # Skip past the directive.
      i += len(directive)
    
      # Get the trailing whitespace.
      j = skip_ws(s,i)
      ws = s[i:j]
      
      # Put the opening @+doc or @-doc sentinel, including trailing whitespace.
      at.putSentinel(sentinel + ws)
    
      # Put the opening comment.
      if at.endSentinelComment:
        at.putIndent(at.indent)
        at.os(at.startSentinelComment) ; at.onl()
    
      # Put an @nonl sentinel if there is significant text following @doc or @.
      if not is_nl(s,j):
        # Doesn't work if we are using block comments.
        at.putSentinel("@nonl")
        at.putDocLine(s,j)
  #@nonl
  #@-node:putStartDocLine
  #@+node:putDocLine
  def putDocLine (self,s,i):
    
    """Handle one line of a doc part.
    
    Output complete lines and split long lines and queue pending lines.
    Inserted newlines are always preceded by whitespace."""
    
    at = self
    j = skip_line(s,i)
    s = s[i:j]
  
    if at.endSentinelComment:
      leading = at.indent
    else:
      leading = at.indent + len(at.startSentinelComment) + 1
  
    if not s or s[0] == '\n':
      # A blank line.
      at.putBlankDocLine()
    else:
      #@    << append words to pending line, splitting the line if needed >>
      #@+node:<< append words to pending line, splitting the line if needed >>
      #@+at 
      #@nonl
      # All inserted newlines are preceeded by whitespace:
      # we remove trailing whitespace from lines that have not been 
      # split.
      #@-at
      #@@c
      
      i = 0
      while i < len(s):
      
        # Scan to the next word.
        word1 = i # Start of the current word.
        word2 = i = skip_ws(s,i)
        while i < len(s) and s[i] not in (' ','\t'):
          i += 1
        word3 = i = skip_ws(s,i)
        # trace(s[word1:i])
        
        if leading + word3 - word1 + len(''.join(at.pending)) >= at.page_width:
          if at.pending:
            # trace("splitting long line.")
            # Ouput the pending line, and start a new line.
            at.putPending(split=true)
            at.pending = [s[word2:word3]]
          else:
            # Output a long word on a line by itself.
            # trace("long word:",s[word2:word3])
            at.pending = [s[word2:word3]]
            at.putPending(split=true)
        else:
          # Append the entire word to the pending line.
          # trace("appending",s[word1:word3])
          at.pending.append(s[word1:word3])
            
      # Output the remaining line: no more is left.
      at.putPending(split=false)
      #@nonl
      #@-node:<< append words to pending line, splitting the line if needed >>
      #@nl
  #@-node:putDocLine
  #@+node:putEndDocLine
  def putEndDocLine (self):
    
    """Write the conclusion of a doc part."""
    
    at = self
    
    at.putPending(split=false)
    
    # Put the closing delimiter if we are using block comments.
    if at.endSentinelComment:
      at.putIndent(at.indent)
      at.os(at.endSentinelComment)
      at.onl() # Note: no trailing whitespace.
  
    sentinel = choose(at.docKind == docDirective,"@-doc","@-at")
    at.putSentinel(sentinel)
  #@nonl
  #@-node:putEndDocLine
  #@+node:putPending
  def putPending (self,split):
    
    """Write the pending part of a doc part.
    
    We retain trailing whitespace iff the split flag is true."""
    
    at = self ; s = ''.join(at.pending) ; at.pending = []
    
    # trace("split",`s`)
    
    # Remove trailing newline temporarily.  We'll add it back later.
    if s and s[-1] == '\n':
      s = s[:-1]
  
    if not split:
      s = s.rstrip()
      if not s:
        return
  
    at.putIndent(at.indent)
  
    if not at.endSentinelComment:
      at.os(at.startSentinelComment) ; at.oblank()
  
    at.os(s) ; at.onl()
  #@nonl
  #@-node:putPending
  #@+node:directiveKind
  # Returns the kind of at-directive or noDirective.
  
  def directiveKind(self,s,i):
  
    at = self
    n = len(s)
    if i >= n or s[i] != '@':
      j = skip_ws(s,i)
      if match_word(s,j,"@others"):
        return othersDirective
      else:
        return noDirective
  
    table = (
      ("@c",cDirective),
      ("@code",codeDirective),
      ("@doc",docDirective),
      ("@end_raw",endRawDirective),
      ("@others",othersDirective),
      ("@raw",rawDirective))
  
    # This code rarely gets executed, so simple code suffices.
    if i+1 >= n or match(s,i,"@ ") or match(s,i,"@\t") or match(s,i,"@\n"):
      # 10/25/02: @space is not recognized in cweb mode.
      # 11/15/02: Noweb doc parts are _never_ scanned in cweb mode.
      return choose(at.language=="cweb",
        noDirective,atDirective)
  
    # 10/28/02: @c and @(nonalpha) are not recognized in cweb mode.
    # We treat @(nonalpha) separately because @ is in the colorizer table.
    if at.language=="cweb" and (
      match_word(s,i,"@c") or
      i+1>= n or s[i+1] not in string.ascii_letters):
      return noDirective
  
    for name,directive in table:
      if match_word(s,i,name):
        return directive
  
    # 10/14/02: return miscDirective only for real directives.
    for name in leoColor.leoKeywords:
      if match_word(s,i,name):
        return miscDirective
  
    return noDirective
  #@nonl
  #@-node:directiveKind
  #@+node:hasSectionName
  def findSectionName(self,s,i):
    
    end = s.find('\n',i)
    if end == -1:
      n1 = s.find("<<",i)
      n2 = s.find(">>",i)
    else:
      n1 = s.find("<<",i,end)
      n2 = s.find(">>",i,end)
  
    return -1 < n1 < n2, n1, n2
  #@nonl
  #@-node:hasSectionName
  #@+node:os, onl, etc.
  def oblank(self):
    self.os(' ')
  
  def oblanks(self,n):
    self.os(' ' * abs(n))
  
  def onl(self):
    self.os(self.output_newline)
    
  def onl_sent(self):
    if self.sentinels:
      self.onl()
    
  def os (self,s):
    if s and self.outputFile:
      try:
        s = toEncodedString(s,self.encoding,reportErrors=true)
        self.outputFile.write(s)
      except:
        es("exception writing:" + `s`)
        es_exception()
  
  def otabs(self,n):
    self.os('\t' * abs(n))
  #@nonl
  #@-node:os, onl, etc.
  #@+node:putDirective  (handles @delims) 4,x
  #@+at 
  #@nonl
  # It is important for PHP and other situations that @first and @last 
  # directives get translated to verbatim lines that do _not_ include what 
  # follows the @first & @last directives.
  #@-at
  #@@c
  
  def putDirective(self,s,i):
    
    """Output a sentinel a directive or reference s."""
  
    tag = "@delims"
    assert(i < len(s) and s[i] == '@')
    k = i
    j = skip_to_end_of_line(s,i)
    directive = s[i:j]
  
    if match_word(s,k,"@delims"):
      #@    << handle @delims >>
      #@+node:<< handle @delims >>
      # Put a space to protect the last delim.
      self.putSentinel(directive + " ") # 10/23/02: put @delims, not @@delims
      
      # Skip the keyword and whitespace.
      j = i = skip_ws(s,k+len(tag))
      
      # Get the first delim.
      while i < len(s) and not is_ws(s[i]) and not is_nl(s,i):
        i += 1
      if j < i:
        self.startSentinelComment = s[j:i]
        # Get the optional second delim.
        j = i = skip_ws(s,i)
        while i < len(s) and not is_ws(s[i]) and not is_nl(s,i):
          i += 1
        self.endSentinelComment = choose(j<i, s[j:i], "")
      else:
        self.writeError("Bad @delims directive")
      #@nonl
      #@-node:<< handle @delims >>
      #@nl
    elif match_word(s,k,"@last"):
      self.putSentinel("@@last") # 10/27/03: Convert to an verbatim line _without_ anything else.
    elif match_word(s,k,"@first"):
      self.putSentinel("@@first") # 10/27/03: Convert to an verbatim line _without_ anything else.
    else:
      self.putSentinel("@" + directive)
  
    i = skip_line(s,k)
    return i
  #@nonl
  #@-node:putDirective  (handles @delims) 4,x
  #@-others
  #@nonl
  #@-node:<< class baseNewDerivedFile methods >>
  #@nl
  
class newDerivedFile(baseNewDerivedFile):
  pass # May be overridden in plugins.
#@nonl
#@-node:@file leoAtFile.py 
#@-leo
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.