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