blob: 2c3371e08c52d78cb6c673744b24829c88b6842d [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 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 Rossumfdfa2b51994-07-08 09:14:54 +0000136 if self.empty:
137 # One or more previous lines were empty
138 # -- insert one blank line in the text
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000139 self._insert_prop('\n')
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000140 self.lineno = self.lineno + 1
141 self.empty = 0
142 if not propline:
143 # No properties
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000144 self._insert_prop(textline)
Guido van Rossumc618ed91994-07-12 09:00:42 +0000145 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:])
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000156 self.lineno = self.lineno + 1
Guido van Rossumc618ed91994-07-12 09:00:42 +0000157 self['state'] = savestate
Guido van Rossumf09b7701994-07-06 21:17:21 +0000158
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000159 # Insert a string at the end, with at most one property (tag)
160 def _insert_prop(self, str, prop = ' '):
Guido van Rossum9755b261994-07-08 09:17:26 +0000161 here = self.index('end')
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000162 self.insert('end', str)
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000163 tags = self.tag_names(here)
164 for tag in tags:
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000165 self.tag_remove(tag, here, 'end')
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000166 if prop != ' ':
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000167 self.tag_add(prop, here, 'end')
Guido van Rossumf09b7701994-07-06 21:17:21 +0000168
169# Readonly Man Page class -- disables editing, otherwise the same
170class ReadonlyManPage(EditableManPage):
171
Guido van Rossum7d5e4211994-07-08 14:15:05 +0000172 # Initialize instance
Guido van Rossumf09b7701994-07-06 21:17:21 +0000173 def __init__(self, master=None, cnf={}):
Guido van Rossumc618ed91994-07-12 09:00:42 +0000174 EditableManPage.__init__(self, master,
175 (cnf, {'state': 'disabled'}))
Guido van Rossumf09b7701994-07-06 21:17:21 +0000176
177# Alias
178ManPage = 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
183def 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
213if __name__ == '__main__':
214 test()