home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Tools / Languages / Python 1.1 / Demo / tkinter / guido / ManPage.py < prev    next >
Encoding:
Python Source  |  1994-07-12  |  5.4 KB  |  215 lines  |  [TEXT/R*ch]

  1. # Widget to display a man page
  2.  
  3. import regex
  4. from Tkinter import *
  5. from ScrolledText import ScrolledText
  6.  
  7. # XXX These fonts may have to be changed to match your system
  8. BOLDFONT = '*-Courier-Bold-R-Normal-*-120-*'
  9. ITALICFONT = '*-Courier-Medium-O-Normal-*-120-*'
  10.  
  11. # XXX Recognizing footers is system dependent
  12. # (This one works for IRIX 5.2 and Solaris 2.2)
  13. footerprog = regex.compile(
  14.     '^     Page [1-9][0-9]*[ \t]+\|^.*Last change:.*[1-9][0-9]*\n')
  15. emptyprog = regex.compile('^[ \t]*\n')
  16. ulprog = regex.compile('^[ \t]*[Xv!_][Xv!_ \t]*\n')
  17.  
  18. # Basic Man Page class -- does not disable editing
  19. class EditableManPage(ScrolledText):
  20.  
  21.     # Initialize instance
  22.     def __init__(self, master=None, cnf={}):
  23.         # Initialize base class
  24.         ScrolledText.__init__(self, master, cnf)
  25.  
  26.         # Define tags for formatting styles
  27.         self.tag_config('X', {'font': BOLDFONT})
  28.         self.tag_config('!', {'font': ITALICFONT})
  29.         self.tag_config('_', {'underline': 1})
  30.  
  31.         # Set state to idle
  32.         self.fp = None
  33.         self.lineno = 0
  34.  
  35.     # Test whether we are busy parsing a file
  36.     def busy(self):
  37.         return self.fp != None
  38.  
  39.     # Ensure we're not busy
  40.     def kill(self):
  41.         if self.busy():
  42.             self._endparser()
  43.  
  44.     # Parse a file, in the background
  45.     def asyncparsefile(self, fp):
  46.         self._startparser(fp)
  47.         self.tk.createfilehandler(fp, tkinter.READABLE,
  48.                       self._filehandler)
  49.  
  50.     parsefile = asyncparsefile    # Alias
  51.  
  52.     # I/O handler used by background parsing
  53.     def _filehandler(self, fp, mask):
  54.         nextline = self.fp.readline()
  55.         if not nextline:
  56.             self._endparser()
  57.             return
  58.         self._parseline(nextline)
  59.  
  60.     # Parse a file, now (cannot be aborted)
  61.     def syncparsefile(self, fp):
  62.         from select import select
  63.         def avail(fp=fp, tout=0.0, select=select):
  64.             return select([fp], [], [], tout)[0]
  65.         height = self.getint(self['height'])
  66.         self._startparser(fp)
  67.         while 1:
  68.             nextline = fp.readline()
  69.             if not nextline:
  70.                 break
  71.             self._parseline(nextline)
  72.         self._endparser()
  73.  
  74.     # Initialize parsing from a particular file -- must not be busy
  75.     def _startparser(self, fp):
  76.         if self.busy():
  77.             raise RuntimeError, 'startparser: still busy'
  78.         fp.fileno()        # Test for file-ness
  79.         self.fp = fp
  80.         self.lineno = 0
  81.         self.ok = 0
  82.         self.empty = 0
  83.         self.buffer = None
  84.         savestate = self['state']
  85.         self['state'] = 'normal'
  86.         self.delete('1.0', 'end')
  87.         self['state'] = savestate
  88.  
  89.     # End parsing -- must be busy, need not be at EOF
  90.     def _endparser(self):
  91.         if not self.busy():
  92.             raise RuntimeError, 'endparser: not busy'
  93.         if self.buffer:
  94.             self._parseline('')
  95.         try:
  96.             self.tk.deletefilehandler(self.fp)
  97.         except TclError, msg:
  98.             pass
  99.         self.fp.close()
  100.         self.fp = None
  101.         del self.ok, self.empty, self.buffer
  102.  
  103.     # Parse a single line
  104.     def _parseline(self, nextline):
  105.         if not self.buffer:
  106.             # Save this line -- we need one line read-ahead
  107.             self.buffer = nextline
  108.             return
  109.         if emptyprog.match(self.buffer) >= 0:
  110.             # Buffered line was empty -- set a flag
  111.             self.empty = 1
  112.             self.buffer = nextline
  113.             return
  114.         textline = self.buffer
  115.         if ulprog.match(nextline) >= 0:
  116.             # Next line is properties for buffered line
  117.             propline = nextline
  118.             self.buffer = None
  119.         else:
  120.             # Next line is read-ahead
  121.             propline = None
  122.             self.buffer = nextline
  123.         if not self.ok:
  124.             # First non blank line after footer must be header
  125.             # -- skip that too
  126.             self.ok = 1
  127.             self.empty = 0
  128.             return
  129.         if footerprog.match(textline) >= 0:
  130.             # Footer -- start skipping until next non-blank line
  131.             self.ok = 0
  132.             self.empty = 0
  133.             return
  134.         savestate = self['state']
  135.         self['state'] = 'normal'
  136.         if self.empty:
  137.             # One or more previous lines were empty
  138.             # -- insert one blank line in the text
  139.             self._insert_prop('\n')
  140.             self.lineno = self.lineno + 1
  141.             self.empty = 0
  142.         if not propline:
  143.             # No properties
  144.             self._insert_prop(textline)
  145.         else:
  146.             # Search for properties
  147.             p = ''
  148.             j = 0
  149.             for i in range(min(len(propline), len(textline))):
  150.                 if propline[i] != p:
  151.                     if j < i:
  152.                         self._insert_prop(textline[j:i], p)
  153.                         j = i
  154.                     p = propline[i]
  155.             self._insert_prop(textline[j:])
  156.         self.lineno = self.lineno + 1
  157.         self['state'] = savestate
  158.  
  159.     # Insert a string at the end, with at most one property (tag)
  160.     def _insert_prop(self, str, prop = ' '):
  161.         here = self.index('end')
  162.         self.insert('end', str)
  163.         tags = self.tag_names(here)
  164.         for tag in tags:
  165.             self.tag_remove(tag, here, 'end')
  166.         if prop != ' ':
  167.             self.tag_add(prop, here, 'end')
  168.  
  169. # Readonly Man Page class -- disables editing, otherwise the same
  170. class ReadonlyManPage(EditableManPage):
  171.  
  172.     # Initialize instance
  173.     def __init__(self, master=None, cnf={}):
  174.         EditableManPage.__init__(self, master,
  175.                      (cnf, {'state': 'disabled'}))
  176.  
  177. # Alias
  178. ManPage = ReadonlyManPage
  179.  
  180. # Test program.
  181. # usage: ManPage [manpage]; or ManPage [-f] file
  182. # -f means that the file is nroff -man output run through ul -i
  183. def test():
  184.     import os
  185.     import sys
  186.     # XXX This directory may be different on your system
  187.     MANDIR = '/usr/local/man/mann'
  188.     DEFAULTPAGE = 'Tcl'
  189.     formatted = 0
  190.     if sys.argv[1:] and sys.argv[1] == '-f':
  191.         formatted = 1
  192.         del sys.argv[1]
  193.     if sys.argv[1:]:
  194.         name = sys.argv[1]
  195.     else:
  196.         name = DEFAULTPAGE
  197.     if not formatted:
  198.         if name[-2:-1] != '.':
  199.             name = name + '.n'
  200.         name = os.path.join(MANDIR, name)
  201.     root = Tk()
  202.     root.minsize(1, 1)
  203.     manpage = ManPage(root, {'relief': 'sunken', 'bd': 2,
  204.                  Pack: {'expand': 1, 'fill': 'both'}})
  205.     if formatted:
  206.         fp = open(name, 'r')
  207.     else:
  208.         fp = os.popen('nroff -man %s | ul -i' % name, 'r')
  209.     manpage.parsefile(fp)
  210.     root.mainloop()
  211.  
  212. # Run the test program when called as a script
  213. if __name__ == '__main__':
  214.     test()
  215.