blob: b6ade64b3a9d168ff8e39229a09803e0e31e0a59 [file] [log] [blame]
Guido van Rossumf09b7701994-07-06 21:17:21 +00001# Widget to display a man page
2
3import regex
4from Tkinter import *
5from ScrolledText import ScrolledText
6
7# XXX These fonts may have to be changed to match your system
8BOLDFONT = '*-Courier-Bold-R-Normal-*-120-*'
9ITALICFONT = '*-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)
13footerprog = regex.compile(
14 '^ Page [1-9][0-9]*[ \t]+\|^.*Last change:.*[1-9][0-9]*\n')
15emptyprog = regex.compile('^[ \t]*\n')
16ulprog = regex.compile('^[ \t]*[Xv!_][Xv!_ \t]*\n')
17
18# Basic Man Page class -- does not disable editing
19class EditableManPage(ScrolledText):
20
Guido van Rossum7d5e4211994-07-08 14:15:05 +000021 # Initialize instance
Guido van Rossumf09b7701994-07-06 21:17:21 +000022 def __init__(self, master=None, cnf={}):
23 # Initialize base class
24 ScrolledText.__init__(self, master, cnf)
25
26 # Define tags for formatting styles
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000027 self.tag_config('X', {'font': BOLDFONT})
28 self.tag_config('!', {'font': ITALICFONT})
29 self.tag_config('_', {'underline': 1})
Guido van Rossumf09b7701994-07-06 21:17:21 +000030
Guido van Rossum7d5e4211994-07-08 14:15:05 +000031 # 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):
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000062 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'])
Guido van Rossum7d5e4211994-07-08 14:15:05 +000066 self._startparser(fp)
Guido van Rossumf09b7701994-07-06 21:17:21 +000067 while 1:
Guido van Rossumf09b7701994-07-06 21:17:21 +000068 nextline = fp.readline()
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000069 if not nextline:
70 break
Guido van Rossum7d5e4211994-07-08 14:15:05 +000071 self._parseline(nextline)
72 self._endparser()
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000073
Guido van Rossum7d5e4211994-07-08 14:15:05 +000074 # 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
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000080 self.lineno = 0
81 self.ok = 0
82 self.empty = 0
83 self.buffer = None
Guido van Rossum7d5e4211994-07-08 14:15:05 +000084 self.delete('1.0', 'end')
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000085
Guido van Rossum7d5e4211994-07-08 14:15:05 +000086 # End parsing -- must be busy, need not be at EOF
87 def _endparser(self):
88 if not self.busy():
89 raise RuntimeError, 'endparser: not busy'
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000090 if self.buffer:
Guido van Rossum7d5e4211994-07-08 14:15:05 +000091 self._parseline('')
92 try:
93 self.tk.deletefilehandler(self.fp)
94 except TclError, msg:
95 pass
96 self.fp.close()
97 self.fp = None
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000098 del self.ok, self.empty, self.buffer
99
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000100 # Parse a single line
101 def _parseline(self, nextline):
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000102 if not self.buffer:
103 # Save this line -- we need one line read-ahead
104 self.buffer = nextline
105 return
106 if emptyprog.match(self.buffer) >= 0:
107 # Buffered line was empty -- set a flag
108 self.empty = 1
109 self.buffer = nextline
110 return
111 textline = self.buffer
112 if ulprog.match(nextline) >= 0:
113 # Next line is properties for buffered line
114 propline = nextline
115 self.buffer = None
116 else:
117 # Next line is read-ahead
118 propline = None
119 self.buffer = nextline
120 if not self.ok:
121 # First non blank line after footer must be header
122 # -- skip that too
123 self.ok = 1
124 self.empty = 0
125 return
126 if footerprog.match(textline) >= 0:
127 # Footer -- start skipping until next non-blank line
128 self.ok = 0
129 self.empty = 0
130 return
131 if self.empty:
132 # One or more previous lines were empty
133 # -- insert one blank line in the text
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000134 self._insert_prop('\n')
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000135 self.lineno = self.lineno + 1
136 self.empty = 0
137 if not propline:
138 # No properties
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000139 self._insert_prop(textline)
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000140 self.lineno = self.lineno + 1
141 return
142 # Search for properties
143 p = ''
144 j = 0
145 for i in range(min(len(propline), len(textline))):
146 if propline[i] != p:
147 if j < i:
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000148 self._insert_prop(textline[j:i], p)
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000149 j = i
150 p = propline[i]
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000151 self._insert_prop(textline[j:])
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000152 self.lineno = self.lineno + 1
Guido van Rossumf09b7701994-07-06 21:17:21 +0000153
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000154 # Insert a string at the end, with at most one property (tag)
155 def _insert_prop(self, str, prop = ' '):
Guido van Rossum9755b261994-07-08 09:17:26 +0000156 here = self.index('end')
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000157 self.insert('end', str)
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000158 tags = self.tag_names(here)
159 for tag in tags:
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000160 self.tag_remove(tag, here, 'end')
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000161 if prop != ' ':
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000162 self.tag_add(prop, here, 'end')
Guido van Rossumf09b7701994-07-06 21:17:21 +0000163
164# Readonly Man Page class -- disables editing, otherwise the same
165class ReadonlyManPage(EditableManPage):
166
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000167 # Initialize instance
Guido van Rossumf09b7701994-07-06 21:17:21 +0000168 def __init__(self, master=None, cnf={}):
169 # Initialize base class
170 EditableManPage.__init__(self, master, cnf)
171
172 # Make the text readonly
Guido van Rossum72cb0201994-07-06 21:53:18 +0000173 self.bind('<Any-KeyPress>', self.modify_cb)
174 self.bind('<Return>', self.modify_cb)
175 self.bind('<BackSpace>', self.modify_cb)
176 self.bind('<Delete>', self.modify_cb)
177 self.bind('<Control-h>', self.modify_cb)
178 self.bind('<Control-d>', self.modify_cb)
179 self.bind('<Control-v>', self.modify_cb)
Guido van Rossumf09b7701994-07-06 21:17:21 +0000180
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000181 # You could override this to ring the bell, etc.
Guido van Rossumf09b7701994-07-06 21:17:21 +0000182 def modify_cb(self, e):
183 pass
184
185# Alias
186ManPage = ReadonlyManPage
187
188# Test program.
189# usage: ManPage [manpage]; or ManPage [-f] file
190# -f means that the file is nroff -man output run through ul -i
191def test():
192 import os
193 import sys
194 # XXX This directory may be different on your system
195 MANDIR = '/usr/local/man/mann'
196 DEFAULTPAGE = 'Tcl'
197 formatted = 0
198 if sys.argv[1:] and sys.argv[1] == '-f':
199 formatted = 1
200 del sys.argv[1]
201 if sys.argv[1:]:
202 name = sys.argv[1]
203 else:
204 name = DEFAULTPAGE
205 if not formatted:
206 if name[-2:-1] != '.':
207 name = name + '.n'
208 name = os.path.join(MANDIR, name)
209 root = Tk()
210 root.minsize(1, 1)
211 manpage = ManPage(root, {'relief': 'sunken', 'bd': 2,
212 Pack: {'expand': 1, 'fill': 'both'}})
213 if formatted:
214 fp = open(name, 'r')
215 else:
216 fp = os.popen('nroff -man %s | ul -i' % name, 'r')
217 manpage.parsefile(fp)
218 root.mainloop()
219
220# Run the test program when called as a script
221if __name__ == '__main__':
222 test()