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