blob: b8b6682e8108a551449a47888803124a69db4105 [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 Rossum69428f11995-09-07 19:46:43 +000027 self.tag_config('X', {'underline': 1})
28 self.tag_config('!', {'font': BOLDFONT})
29 self.tag_config('_', {'font': ITALICFONT})
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 Rossumc618ed91994-07-12 09:00:42 +000084 savestate = self['state']
85 self['state'] = 'normal'
Guido van Rossum7d5e4211994-07-08 14:15:05 +000086 self.delete('1.0', 'end')
Guido van Rossumc618ed91994-07-12 09:00:42 +000087 self['state'] = savestate
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000088
Guido van Rossum7d5e4211994-07-08 14:15:05 +000089 # 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'
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000093 if self.buffer:
Guido van Rossum7d5e4211994-07-08 14:15:05 +000094 self._parseline('')
95 try:
96 self.tk.deletefilehandler(self.fp)
97 except TclError, msg:
98 pass
99 self.fp.close()
100 self.fp = None
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000101 del self.ok, self.empty, self.buffer
102
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000103 # Parse a single line
104 def _parseline(self, nextline):
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000105 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
Guido van Rossumc618ed91994-07-12 09:00:42 +0000134 savestate = self['state']
135 self['state'] = 'normal'
Guido van Rossum69428f11995-09-07 19:46:43 +0000136 if TkVersion >= 4.0:
137 self.mark_set('insert', 'end-1c')
138 else:
139 self.mark_set('insert', 'end')
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000140 if self.empty:
141 # One or more previous lines were empty
142 # -- insert one blank line in the text
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000143 self._insert_prop('\n')
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000144 self.lineno = self.lineno + 1
145 self.empty = 0
146 if not propline:
147 # No properties
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000148 self._insert_prop(textline)
Guido van Rossumc618ed91994-07-12 09:00:42 +0000149 else:
150 # Search for properties
151 p = ''
152 j = 0
153 for i in range(min(len(propline), len(textline))):
154 if propline[i] != p:
155 if j < i:
156 self._insert_prop(textline[j:i], p)
157 j = i
158 p = propline[i]
159 self._insert_prop(textline[j:])
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000160 self.lineno = self.lineno + 1
Guido van Rossumc618ed91994-07-12 09:00:42 +0000161 self['state'] = savestate
Guido van Rossumf09b7701994-07-06 21:17:21 +0000162
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000163 # Insert a string at the end, with at most one property (tag)
164 def _insert_prop(self, str, prop = ' '):
Guido van Rossum69428f11995-09-07 19:46:43 +0000165 here = self.index(AtInsert())
166 self.insert(AtInsert(), str)
167 if TkVersion <= 4.0:
168 tags = self.tag_names(here)
169 for tag in tags:
170 self.tag_remove(tag, here, AtInsert())
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000171 if prop != ' ':
Guido van Rossum69428f11995-09-07 19:46:43 +0000172 self.tag_add(prop, here, AtInsert())
Guido van Rossumf09b7701994-07-06 21:17:21 +0000173
174# Readonly Man Page class -- disables editing, otherwise the same
175class ReadonlyManPage(EditableManPage):
176
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000177 # Initialize instance
Guido van Rossumf09b7701994-07-06 21:17:21 +0000178 def __init__(self, master=None, cnf={}):
Guido van Rossumc618ed91994-07-12 09:00:42 +0000179 EditableManPage.__init__(self, master,
180 (cnf, {'state': 'disabled'}))
Guido van Rossumf09b7701994-07-06 21:17:21 +0000181
182# Alias
183ManPage = ReadonlyManPage
184
185# Test program.
186# usage: ManPage [manpage]; or ManPage [-f] file
187# -f means that the file is nroff -man output run through ul -i
188def test():
189 import os
190 import sys
191 # XXX This directory may be different on your system
192 MANDIR = '/usr/local/man/mann'
193 DEFAULTPAGE = 'Tcl'
194 formatted = 0
195 if sys.argv[1:] and sys.argv[1] == '-f':
196 formatted = 1
197 del sys.argv[1]
198 if sys.argv[1:]:
199 name = sys.argv[1]
200 else:
201 name = DEFAULTPAGE
202 if not formatted:
203 if name[-2:-1] != '.':
204 name = name + '.n'
205 name = os.path.join(MANDIR, name)
206 root = Tk()
207 root.minsize(1, 1)
208 manpage = ManPage(root, {'relief': 'sunken', 'bd': 2,
209 Pack: {'expand': 1, 'fill': 'both'}})
210 if formatted:
211 fp = open(name, 'r')
212 else:
213 fp = os.popen('nroff -man %s | ul -i' % name, 'r')
214 manpage.parsefile(fp)
215 root.mainloop()
216
217# Run the test program when called as a script
218if __name__ == '__main__':
219 test()