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