blob: 9c341b62747541e7d6407c98c40ebcf2c88980f0 [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
21 def __init__(self, master=None, cnf={}):
22 # Initialize base class
23 ScrolledText.__init__(self, master, cnf)
24
25 # Define tags for formatting styles
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000026 self.tag_config('X', {'font': BOLDFONT})
27 self.tag_config('!', {'font': ITALICFONT})
28 self.tag_config('_', {'underline': 1})
Guido van Rossumf09b7701994-07-06 21:17:21 +000029
Guido van Rossumf09b7701994-07-06 21:17:21 +000030 def parsefile(self, fp):
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000031 if hasattr(self, 'buffer'):
32 raise RuntimeError, 'Still busy parsing!'
33 from select import select
34 def avail(fp=fp, tout=0.0, select=select):
35 return select([fp], [], [], tout)[0]
36 height = self.getint(self['height'])
37 self.startparser()
Guido van Rossumf09b7701994-07-06 21:17:21 +000038 while 1:
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000039 if self.lineno < height or \
40 self.lineno%10 == 0 or not avail():
41 self.update()
Guido van Rossumf09b7701994-07-06 21:17:21 +000042 nextline = fp.readline()
Guido van Rossumfdfa2b51994-07-08 09:14:54 +000043 if not nextline:
44 break
45 self.parseline(nextline)
46 self.endparser()
47 self.update()
48
49 def startparser(self):
50 self.lineno = 0
51 self.ok = 0
52 self.empty = 0
53 self.buffer = None
54
55 def endparser(self):
56 if self.buffer:
57 self.parseline('')
58 del self.ok, self.empty, self.buffer
59
60 def parseline(self, nextline):
61 if not self.buffer:
62 # Save this line -- we need one line read-ahead
63 self.buffer = nextline
64 return
65 if emptyprog.match(self.buffer) >= 0:
66 # Buffered line was empty -- set a flag
67 self.empty = 1
68 self.buffer = nextline
69 return
70 textline = self.buffer
71 if ulprog.match(nextline) >= 0:
72 # Next line is properties for buffered line
73 propline = nextline
74 self.buffer = None
75 else:
76 # Next line is read-ahead
77 propline = None
78 self.buffer = nextline
79 if not self.ok:
80 # First non blank line after footer must be header
81 # -- skip that too
82 self.ok = 1
83 self.empty = 0
84 return
85 if footerprog.match(textline) >= 0:
86 # Footer -- start skipping until next non-blank line
87 self.ok = 0
88 self.empty = 0
89 return
90 if self.empty:
91 # One or more previous lines were empty
92 # -- insert one blank line in the text
93 self.insert_prop('\n')
94 self.lineno = self.lineno + 1
95 self.empty = 0
96 if not propline:
97 # No properties
98 self.insert_prop(textline)
99 self.lineno = self.lineno + 1
100 return
101 # Search for properties
102 p = ''
103 j = 0
104 for i in range(min(len(propline), len(textline))):
105 if propline[i] != p:
106 if j < i:
107 self.insert_prop(textline[j:i], p)
108 j = i
109 p = propline[i]
110 self.insert_prop(textline[j:])
111 self.lineno = self.lineno + 1
Guido van Rossumf09b7701994-07-06 21:17:21 +0000112
113 def insert_prop(self, str, prop = ' '):
Guido van Rossum72cb0201994-07-06 21:53:18 +0000114 here = self.index(AtInsert())
Guido van Rossumfdfa2b51994-07-08 09:14:54 +0000115 self.insert(AtInsert(), str[0])
116 tags = self.tag_names(here)
117 for tag in tags:
118 self.tag_remove(tag, here)
119 if prop != ' ':
120 self.tag_add(prop, here)
121 self.insert(AtInsert(), str[1:])
Guido van Rossumf09b7701994-07-06 21:17:21 +0000122
123# Readonly Man Page class -- disables editing, otherwise the same
124class ReadonlyManPage(EditableManPage):
125
126 def __init__(self, master=None, cnf={}):
127 # Initialize base class
128 EditableManPage.__init__(self, master, cnf)
129
130 # Make the text readonly
Guido van Rossum72cb0201994-07-06 21:53:18 +0000131 self.bind('<Any-KeyPress>', self.modify_cb)
132 self.bind('<Return>', self.modify_cb)
133 self.bind('<BackSpace>', self.modify_cb)
134 self.bind('<Delete>', self.modify_cb)
135 self.bind('<Control-h>', self.modify_cb)
136 self.bind('<Control-d>', self.modify_cb)
137 self.bind('<Control-v>', self.modify_cb)
Guido van Rossumf09b7701994-07-06 21:17:21 +0000138
139 def modify_cb(self, e):
140 pass
141
142# Alias
143ManPage = ReadonlyManPage
144
145# Test program.
146# usage: ManPage [manpage]; or ManPage [-f] file
147# -f means that the file is nroff -man output run through ul -i
148def test():
149 import os
150 import sys
151 # XXX This directory may be different on your system
152 MANDIR = '/usr/local/man/mann'
153 DEFAULTPAGE = 'Tcl'
154 formatted = 0
155 if sys.argv[1:] and sys.argv[1] == '-f':
156 formatted = 1
157 del sys.argv[1]
158 if sys.argv[1:]:
159 name = sys.argv[1]
160 else:
161 name = DEFAULTPAGE
162 if not formatted:
163 if name[-2:-1] != '.':
164 name = name + '.n'
165 name = os.path.join(MANDIR, name)
166 root = Tk()
167 root.minsize(1, 1)
168 manpage = ManPage(root, {'relief': 'sunken', 'bd': 2,
169 Pack: {'expand': 1, 'fill': 'both'}})
170 if formatted:
171 fp = open(name, 'r')
172 else:
173 fp = os.popen('nroff -man %s | ul -i' % name, 'r')
174 manpage.parsefile(fp)
175 root.mainloop()
176
177# Run the test program when called as a script
178if __name__ == '__main__':
179 test()