blob: fabd99e276cc33c9f9715074a531271578a8d524 [file] [log] [blame]
Guido van Rossum26a9d371995-03-15 11:26:05 +00001#! /usr/local/bin/python
2
3# Convert GNU texinfo files into HTML, one file per node.
4# Based on Texinfo 2.14.
5# Usage: texi2html [-d] [-d] inputfile outputdirectory
6# The input file must be a complete texinfo file, e.g. emacs.texi.
7# This creates many files (one per info node) in the output directory,
8# overwriting existing files of the same name. All files created have
9# ".html" as their extension.
10
11
12# XXX To do:
13# - handle @comment*** correctly
14# - handle @xref {some words} correctly
15# - handle @ftable correctly (items aren't indexed?)
16# - handle @itemx properly
17# - handle @exdent properly
18# - add links directly to the proper line from indices
19# - check against the definitive list of @-cmds; we still miss (among others):
20# - @set, @clear, @ifset, @ifclear
21# - @defindex (hard)
22# - @c(omment) in the middle of a line (rarely used)
23# - @this* (not really needed, only used in headers anyway)
24# - @today{} (ever used outside title page?)
25
26
27import os
28import regex
29import regsub
30import string
31
32MAGIC = '\\input texinfo'
33
34cmprog = regex.compile('^@\([a-z]+\)\([ \t]\|$\)') # Command (line-oriented)
35blprog = regex.compile('^[ \t]*$') # Blank line
36kwprog = regex.compile('@[a-z]+') # Keyword (embedded, usually with {} args)
37spprog = regex.compile('[\n@{}&<>]') # Special characters in running text
38miprog = regex.compile( \
39 '^\* \([^:]*\):\(:\|[ \t]*\([^\t,\n.]+\)\([^ \t\n]*\)\)[ \t\n]*')
40 # menu item (Yuck!)
41
42class TexinfoParser:
43
44 # Initialize an instance
45 def __init__(self):
46 self.unknown = {} # statistics about unknown @-commands
47 self.debugging = 0 # larger values produce more output
48 self.nodefp = None # open file we're writing to
49 self.savetext = None # If not None, save text head instead
50 self.dirname = 'tmp' # directory where files are created
51 self.includedir = '.' # directory to search @include files
52 self.nodename = '' # name of current node
53 self.topname = '' # name of top node (first node seen)
54 self.title = '' # title of this whole Texinfo tree
55 self.resetindex() # Reset all indices
56 self.contents = [] # Reset table of contents
57 self.numbering = [] # Reset section numbering counters
58 self.nofill = 0 # Normal operation: fill paragraphs
59 # XXX The following should be reset per node?!
60 self.footnotes = [] # Reset list of footnotes
61 self.itemarg = None # Reset command used by @item
62 self.itemnumber = None # Reset number for @item in @enumerate
63 self.itemindex = None # Reset item index name
64
65 # Set (output) directory name
66 def setdirname(self, dirname):
67 self.dirname = dirname
68
69 # Set include directory name
70 def setincludedir(self, includedir):
71 self.includedir = includedir
72
73 # Parse the contents of an entire file
74 def parse(self, fp):
75 line = fp.readline()
76 lineno = 1
77 while line and (line[0] == '%' or blprog.match(line) >= 0):
78 line = fp.readline()
79 lineno = lineno + 1
80 if line[:len(MAGIC)] <> MAGIC:
81 raise SyntaxError, 'file does not begin with '+`MAGIC`
82 self.parserest(fp, lineno)
83
84 # Parse the contents of a file, not expecting a MAGIC header
85 def parserest(self, fp, initial_lineno):
86 lineno = initial_lineno
87 self.done = 0
88 self.skip = 0
89 self.stack = []
90 accu = []
91 while not self.done:
92 line = fp.readline()
93 if not line:
94 if accu:
95 if not self.skip: self.process(accu)
96 accu = []
97 if initial_lineno > 0:
98 print '*** EOF before @bye'
99 break
100 lineno = lineno + 1
101 if cmprog.match(line) >= 0:
102 a, b = cmprog.regs[1]
103 cmd = line[a:b]
104 if cmd in ('noindent', 'refill'):
105 accu.append(line)
106 else:
107 if accu:
108 if not self.skip:
109 self.process(accu)
110 accu = []
111 self.command(line)
112 elif blprog.match(line) >= 0:
113 if accu:
114 if not self.skip:
115 self.process(accu)
116 self.write('<P>\n')
117 accu = []
118 else:
119 # Append the line including trailing \n!
120 accu.append(line)
121 #
122 if self.skip:
123 print '*** Still skipping at the end'
124 if self.stack:
125 print '*** Stack not empty at the end'
126 print '***', self.stack
127
128 # Start saving text in a buffer instead of writing it to a file
129 def startsaving(self):
130 if self.savetext <> None:
131 print '*** Recursively saving text, expect trouble'
132 self.savetext = ''
133
134 # Return the text saved so far and start writing to file again
135 def collectsavings(self):
136 savetext = self.savetext
137 self.savetext = None
138 return savetext or ''
139
140 # Write text to file, or save it in a buffer, or ignore it
141 def write(self, *args):
142 text = string.joinfields(args, '')
143 if self.savetext <> None:
144 self.savetext = self.savetext + text
145 elif self.nodefp:
146 self.nodefp.write(text)
147
148 # Complete the current node -- write footnotes and close file
149 def endnode(self):
150 if self.savetext <> None:
151 print '*** Still saving text at end of node'
152 dummy = self.collectsavings()
153 if self.footnotes:
154 self.writefootnotes()
155 if self.nodefp:
156 self.nodefp.close()
157 self.nodefp = None
158 self.nodename = ''
159
160 # Process a list of lines, expanding embedded @-commands
161 # This mostly distinguishes between menus and normal text
162 def process(self, accu):
163 if self.debugging > 1:
164 print self.skip, self.stack,
165 if accu: print accu[0][:30],
166 if accu[0][30:] or accu[1:]: print '...',
167 print
168 if self.stack and self.stack[-1] == 'menu':
169 # XXX should be done differently
170 for line in accu:
171 if miprog.match(line) < 0:
172 line = string.strip(line) + '\n'
173 self.expand(line)
174 continue
175 (bgn, end), (a, b), (c, d), (e, f), (g, h) = \
176 miprog.regs[:5]
177 label = line[a:b]
178 nodename = line[c:d]
179 if nodename[0] == ':': nodename = label
180 else: nodename = line[e:f]
181 punct = line[g:h]
182 self.write('<DT><A HREF="', \
183 makefile(nodename), \
184 '" TYPE=Menu>', nodename, \
185 '</A>', punct, '\n<DD>')
186 self.expand(line[end:])
187 else:
188 text = string.joinfields(accu, '')
189 self.expand(text)
190
191 # Write a string, expanding embedded @-commands
192 def expand(self, text):
193 stack = []
194 i = 0
195 n = len(text)
196 while i < n:
197 start = i
198 i = spprog.search(text, i)
199 if i < 0:
200 self.write(text[start:])
201 break
202 self.write(text[start:i])
203 c = text[i]
204 i = i+1
205 if c == '\n':
206 if self.nofill > 0:
207 self.write('<P>\n')
208 else:
209 self.write('\n')
210 continue
211 if c == '<':
212 self.write('&lt;')
213 continue
214 if c == '>':
215 self.write('&gt;')
216 continue
217 if c == '&':
218 self.write('&amp;')
219 continue
220 if c == '{':
221 stack.append('')
222 continue
223 if c == '}':
224 if not stack:
225 print '*** Unmatched }'
226 self.write('}')
227 continue
228 cmd = stack[-1]
229 del stack[-1]
230 try:
231 method = getattr(self, 'close_' + cmd)
232 except AttributeError:
233 self.unknown_close(cmd)
234 continue
235 method()
236 continue
237 if c <> '@':
238 # Cannot happen unless spprog is changed
239 raise RuntimeError, 'unexpected funny '+`c`
240 start = i
241 while i < n and text[i] in string.letters: i = i+1
242 if i == start:
243 # @ plus non-letter: literal next character
244 i = i+1
245 c = text[start:i]
246 if c == ':':
247 # `@:' means no extra space after
248 # preceding `.', `?', `!' or `:'
249 pass
250 else:
251 # `@.' means a sentence-ending period;
252 # `@@', `@{', `@}' quote `@', `{', `}'
253 self.write(c)
254 continue
255 cmd = text[start:i]
256 if i < n and text[i] == '{':
257 i = i+1
258 stack.append(cmd)
259 try:
260 method = getattr(self, 'open_' + cmd)
261 except AttributeError:
262 self.unknown_open(cmd)
263 continue
264 method()
265 continue
266 try:
267 method = getattr(self, 'handle_' + cmd)
268 except AttributeError:
269 self.unknown_handle(cmd)
270 continue
271 method()
272 if stack:
273 print '*** Stack not empty at para:', stack
274
275 # --- Handle unknown embedded @-commands ---
276
277 def unknown_open(self, cmd):
278 print '*** No open func for @' + cmd + '{...}'
279 cmd = cmd + '{'
280 self.write('@', cmd)
281 if not self.unknown.has_key(cmd):
282 self.unknown[cmd] = 1
283 else:
284 self.unknown[cmd] = self.unknown[cmd] + 1
285
286 def unknown_close(self, cmd):
287 print '*** No close func for @' + cmd + '{...}'
288 cmd = '}' + cmd
289 self.write('}')
290 if not self.unknown.has_key(cmd):
291 self.unknown[cmd] = 1
292 else:
293 self.unknown[cmd] = self.unknown[cmd] + 1
294
295 def unknown_handle(self, cmd):
296 print '*** No handler for @' + cmd
297 self.write('@', cmd)
298 if not self.unknown.has_key(cmd):
299 self.unknown[cmd] = 1
300 else:
301 self.unknown[cmd] = self.unknown[cmd] + 1
302
303 # XXX The following sections should be ordered as the texinfo docs
304
305 # --- Embedded @-commands without {} argument list --
306
307 def handle_noindent(self): pass
308
309 def handle_refill(self): pass
310
311 # --- Include file handling ---
312
313 def do_include(self, args):
314 file = args
315 file = os.path.join(self.includedir, file)
316 try:
317 fp = open(file, 'r')
318 except IOError, msg:
319 print '*** Can\'t open include file', `file`
320 return
321 if self.debugging:
322 print '--> file', `file`
323 save_done = self.done
324 save_skip = self.skip
325 save_stack = self.stack
326 self.parserest(fp, 0)
327 fp.close()
328 self.done = save_done
329 self.skip = save_skip
330 self.stack = save_stack
331 if self.debugging:
332 print '<-- file', `file`
333
334 # --- Special Insertions ---
335
336 def open_dmn(self): pass
337 def close_dmn(self): pass
338
339 def open_dots(self): self.write('...')
340 def close_dots(self): pass
341
342 def open_bullet(self): self.write('&bullet;')
343 def close_bullet(self): pass
344
345 def open_TeX(self): self.write('TeX')
346 def close_TeX(self): pass
347
348 def open_copyright(self): self.write('(C)')
349 def close_copyright(self): pass
350
351 def open_minus(self): self.write('-')
352 def close_minus(self): pass
353
354 # --- Special Glyphs for Examples ---
355
356 def open_result(self): self.write('=&gt;')
357 def close_result(self): pass
358
359 def open_expansion(self): self.write('==&gt;')
360 def close_expansion(self): pass
361
362 def open_print(self): self.write('-|')
363 def close_print(self): pass
364
365 def open_error(self): self.write('error--&gt;')
366 def close_error(self): pass
367
368 def open_equiv(self): self.write('==')
369 def close_equiv(self): pass
370
371 def open_point(self): self.write('-!-')
372 def close_point(self): pass
373
374 # --- Cross References ---
375
376 def open_pxref(self):
377 self.write('see ')
378 self.startsaving()
379 def close_pxref(self):
380 self.makeref()
381
382 def open_xref(self):
383 self.write('See ')
384 self.startsaving()
385 def close_xref(self):
386 self.makeref()
387
388 def open_ref(self):
389 self.startsaving()
390 def close_ref(self):
391 self.makeref()
392
393 def open_inforef(self):
394 self.write('See info file ')
395 self.startsaving()
396 def close_inforef(self):
397 text = self.collectsavings()
398 args = string.splitfields(text, ',')
399 n = len(args)
400 for i in range(n):
401 args[i] = string.strip(args[i])
402 while len(args) < 3: args.append('')
403 node = args[0]
404 file = args[2]
405 self.write('`', file, '\', node `', node, '\'')
406
407 def makeref(self):
408 text = self.collectsavings()
409 args = string.splitfields(text, ',')
410 n = len(args)
411 for i in range(n):
412 args[i] = string.strip(args[i])
413 while len(args) < 5: args.append('')
414 nodename = label = args[0]
415 if args[2]: label = args[2]
416 file = args[3]
417 title = args[4]
418 href = makefile(nodename)
419 if file:
420 href = '../' + file + '/' + href
421 self.write('<A HREF="', href, '">', label, '</A>')
422
423 # --- Marking Words and Phrases ---
424
425 # --- Other @xxx{...} commands ---
426
427 def open_(self): pass # Used by {text enclosed in braces}
428 def close_(self): pass
429
430 open_asis = open_
431 close_asis = close_
432
433 def open_cite(self): self.write('<CITE>')
434 def close_cite(self): self.write('</CITE>')
435
436 def open_code(self): self.write('<CODE>')
437 def close_code(self): self.write('</CODE>')
438
439 open_t = open_code
440 close_t = close_code
441
442 def open_dfn(self): self.write('<DFN>')
443 def close_dfn(self): self.write('</DFN>')
444
445 def open_emph(self): self.write('<I>')
446 def close_emph(self): self.write('</I>')
447
448 open_i = open_emph
449 close_i = close_emph
450
451 def open_footnote(self):
452 if self.savetext <> None:
453 print '*** Recursive footnote -- expect weirdness'
454 id = len(self.footnotes) + 1
455 self.write('<A NAME="footnoteref', `id`, \
456 '" HREF="#footnotetext', `id`, '">(', `id`, ')</A>')
457 self.savetext = ''
458
459 def close_footnote(self):
460 id = len(self.footnotes) + 1
461 self.footnotes.append(`id`, self.savetext)
462 self.savetext = None
463
464 def writefootnotes(self):
465 self.write('<H2>---------- Footnotes ----------</H2>\n')
466 for id, text in self.footnotes:
467 self.write('<A NAME="footnotetext', id, \
468 '" HREF="#footnoteref', id, '">(', \
469 id, ')</A>\n', text, '<P>\n')
470 self.footnotes = []
471
472 def open_file(self): self.write('<FILE>')
473 def close_file(self): self.write('</FILE>')
474
475 def open_kbd(self): self.write('<KBD>')
476 def close_kbd(self): self.write('</KBD>')
477
478 def open_key(self): self.write('<KEY>')
479 def close_key(self): self.write('</KEY>')
480
481 def open_r(self): self.write('<R>')
482 def close_r(self): self.write('</R>')
483
484 def open_samp(self): self.write('`<SAMP>')
485 def close_samp(self): self.write('</SAMP>\'')
486
487 def open_sc(self): self.write('<SMALLCAPS>')
488 def close_sc(self): self.write('</SMALLCAPS>')
489
490 def open_strong(self): self.write('<B>')
491 def close_strong(self): self.write('</B>')
492
493 open_b = open_strong
494 close_b = close_strong
495
496 def open_var(self): self.write('<VAR>')
497 def close_var(self): self.write('</VAR>')
498
499 def open_w(self): self.write('<NOBREAK>')
500 def close_w(self): self.write('</NOBREAK>')
501
502 open_titlefont = open_
503 close_titlefont = close_
504
505 def command(self, line):
506 a, b = cmprog.regs[1]
507 cmd = line[a:b]
508 args = string.strip(line[b:])
509 if self.debugging > 1:
510 print self.skip, self.stack, '@' + cmd, args
511 try:
512 func = getattr(self, 'do_' + cmd)
513 except AttributeError:
514 try:
515 func = getattr(self, 'bgn_' + cmd)
516 except AttributeError:
517 self.unknown_cmd(cmd, args)
518 return
519 self.stack.append(cmd)
520 func(args)
521 return
522 if not self.skip or cmd == 'end':
523 func(args)
524
525 def unknown_cmd(self, cmd, args):
526 print '*** unknown', '@' + cmd, args
527 if not self.unknown.has_key(cmd):
528 self.unknown[cmd] = 1
529 else:
530 self.unknown[cmd] = self.unknown[cmd] + 1
531
532 def do_end(self, args):
533 words = string.split(args)
534 if not words:
535 print '*** @end w/o args'
536 else:
537 cmd = words[0]
538 if not self.stack or self.stack[-1] <> cmd:
539 print '*** @end', cmd, 'unexpected'
540 else:
541 del self.stack[-1]
542 try:
543 func = getattr(self, 'end_' + cmd)
544 except AttributeError:
545 self.unknown_end(cmd)
546 return
547 func()
548
549 def unknown_end(self, cmd):
550 cmd = 'end ' + cmd
551 print '*** unknown', '@' + cmd
552 if not self.unknown.has_key(cmd):
553 self.unknown[cmd] = 1
554 else:
555 self.unknown[cmd] = self.unknown[cmd] + 1
556
557 # --- Comments ---
558
559 def do_comment(self, args): pass
560 do_c = do_comment
561
562 # --- Conditional processing ---
563
564 def bgn_ifinfo(self, args): pass
565 def end_ifinfo(self): pass
566
567 def bgn_iftex(self, args): self.skip = self.skip + 1
568 def end_iftex(self): self.skip = self.skip - 1
569
570 def bgn_ignore(self, args): self.skip = self.skip + 1
571 def end_ignore(self): self.skip = self.skip - 1
572
573 def bgn_tex(self, args): self.skip = self.skip + 1
574 def end_tex(self): self.skip = self.skip - 1
575
576 # --- Beginning a file ---
577
578 do_finalout = do_comment
579 do_setchapternewpage = do_comment
580 do_setfilename = do_comment
581
582 def do_settitle(self, args):
583 self.title = args
584
585 # --- Ending a file ---
586
587 def do_bye(self, args):
588 self.done = 1
589
590 # --- Title page ---
591
592 def bgn_titlepage(self, args): self.skip = self.skip + 1
593 def end_titlepage(self): self.skip = self.skip - 1
594
595 def do_center(self, args):
596 # Actually not used outside title page...
597 self.write('<H1>', args, '</H1>\n')
598 do_title = do_center
599 do_subtitle = do_center
600 do_author = do_center
601
602 do_vskip = do_comment
603 do_vfill = do_comment
604 do_smallbook = do_comment
605
606 do_paragraphindent = do_comment
607 do_setchapternewpage = do_comment
608 do_headings = do_comment
609 do_footnotestyle = do_comment
610
611 do_evenheading = do_comment
612 do_evenfooting = do_comment
613 do_oddheading = do_comment
614 do_oddfooting = do_comment
615 do_everyheading = do_comment
616 do_everyfooting = do_comment
617
618 # --- Nodes ---
619
620 def do_node(self, args):
621 parts = string.splitfields(args, ',')
622 while len(parts) < 4: parts.append('')
623 for i in range(4): parts[i] = string.strip(parts[i])
624 [name, next, prev, up] = parts[:4]
625 self.endnode()
626 file = self.dirname + '/' + makefile(name)
627 if self.debugging: print '--- writing', file
628 self.nodefp = open(file, 'w')
629 self.nodename = name
630 if not self.topname: self.topname = name
631 title = name
632 if self.title: title = title + ' -- ' + self.title
633 self.write('<TITLE>', title, '</TITLE>\n')
634 self.link('Next', next)
635 self.link('Prev', prev)
636 self.link('Up', up)
637 if self.nodename <> self.topname:
638 self.link('Top', self.topname)
639
640 def link(self, label, nodename):
641 if nodename:
642 if string.lower(nodename) == '(dir)':
643 addr = '../dir.html'
644 else:
645 addr = makefile(nodename)
646 self.write(label, ': <A HREF="', addr, '" TYPE="', \
647 label, '">', nodename, '</A> \n')
648
649 # --- Sectioning commands ---
650
651 def do_chapter(self, args):
652 self.heading('H1', args, 0)
653 def do_unnumbered(self, args):
654 self.heading('H1', args, -1)
655 def do_appendix(self, args):
656 self.heading('H1', args, -1)
657 def do_top(self, args):
658 self.heading('H1', args, -1)
659 def do_chapheading(self, args):
660 self.heading('H1', args, -1)
661 def do_majorheading(self, args):
662 self.heading('H1', args, -1)
663
664 def do_section(self, args):
665 self.heading('H1', args, 1)
666 def do_unnumberedsec(self, args):
667 self.heading('H1', args, -1)
668 def do_appendixsec(self, args):
669 self.heading('H1', args, -1)
670 do_appendixsection = do_appendixsec
671 def do_heading(self, args):
672 self.heading('H1', args, -1)
673
674 def do_subsection(self, args):
675 self.heading('H2', args, 2)
676 def do_unnumberedsubsec(self, args):
677 self.heading('H2', args, -1)
678 def do_appendixsubsec(self, args):
679 self.heading('H2', args, -1)
680 def do_subheading(self, args):
681 self.heading('H2', args, -1)
682
683 def do_subsubsection(self, args):
684 self.heading('H3', args, 3)
685 def do_unnumberedsubsubsec(self, args):
686 self.heading('H3', args, -1)
687 def do_appendixsubsubsec(self, args):
688 self.heading('H3', args, -1)
689 def do_subsubheading(self, args):
690 self.heading('H3', args, -1)
691
692 def heading(self, type, args, level):
693 if level >= 0:
694 while len(self.numbering) <= level:
695 self.numbering.append(0)
696 del self.numbering[level+1:]
697 self.numbering[level] = self.numbering[level] + 1
698 x = ''
699 for i in self.numbering:
700 x = x + `i` + '.'
701 args = x + ' ' + args
702 self.contents.append(level, args, self.nodename)
703 self.write('<', type, '>')
704 self.expand(args)
705 self.write('</', type, '>\n')
706 if self.debugging:
707 print '---', args
708
709 def do_contents(self, args):
710 pass
711 # self.listcontents('Table of Contents', 999)
712
713 def do_shortcontents(self, args):
714 pass
715 # self.listcontents('Short Contents', 0)
716 do_summarycontents = do_shortcontents
717
718 def listcontents(self, title, maxlevel):
719 self.write('<H1>', title, '</H1>\n<UL COMPACT>\n')
720 for level, title, node in self.contents:
721 if level <= maxlevel:
722 self.write('<LI>', '. '*level, '<A HREF="', \
723 makefile(node), '">')
724 self.expand(title)
725 self.write('</A> ', node, '\n')
726 self.write('</UL>\n')
727
728 # --- Page lay-out ---
729
730 # These commands are only meaningful in printed text
731
732 def do_page(self, args): pass
733
734 def do_need(self, args): pass
735
736 def bgn_group(self, args): pass
737 def end_group(self): pass
738
739 # --- Line lay-out ---
740
741 def do_sp(self, args):
742 # Insert <args> blank lines
743 if args:
744 try:
745 n = string.atoi(args)
746 except string.atoi_error:
747 n = 1
748 else:
749 n = 1
750 self.write('<P>\n'*max(n, 0))
751
752 # --- Function and variable definitions ---
753
754 def bgn_deffn(self, args):
755 self.write('<DL><DT>')
756 words = splitwords(args, 2)
757 [category, name], rest = words[:2], words[2:]
758 self.expand('@b{' + name + '}')
759 for word in rest: self.expand(' ' + makevar(word))
760 self.expand(' -- ' + category)
761 self.write('<DD>\n')
762 self.index('fn', name)
763
764 def end_deffn(self):
765 self.write('</DL>\n')
766
767 def bgn_defun(self, args): self.bgn_deffn('Function ' + args)
768 end_defun = end_deffn
769
770 def bgn_defmac(self, args): self.bgn_deffn('Macro ' + args)
771 end_defmac = end_deffn
772
773 def bgn_defspec(self, args): self.bgn_deffn('{Special Form} ' + args)
774 end_defspec = end_deffn
775
776 def bgn_defvr(self, args):
777 self.write('<DL><DT>')
778 words = splitwords(args, 2)
779 [category, name], rest = words[:2], words[2:]
780 self.expand('@code{' + name + '}')
781 # If there are too many arguments, show them
782 for word in rest: self.expand(' ' + word)
783 self.expand(' -- ' + category)
784 self.write('<DD>\n')
785 self.index('vr', name)
786
787 end_defvr = end_deffn
788
789 def bgn_defvar(self, args): self.bgn_defvr('Variable ' + args)
790 end_defvar = end_defvr
791
792 def bgn_defopt(self, args): self.bgn_defvr('{User Option} ' + args)
793 end_defopt = end_defvr
794
795 # --- Ditto for typed languages ---
796
797 def bgn_deftypefn(self, args):
798 self.write('<DL><DT>')
799 words = splitwords(args, 3)
800 [category, datatype, name], rest = words[:3], words[3:]
801 self.expand('@code{' + datatype + '} @b{' + name + '}')
802 for word in rest: self.expand(' ' + makevar(word))
803 self.expand(' -- ' + category)
804 self.write('<DD>\n')
805 self.index('fn', name)
806
807 end_deftypefn = end_deffn
808
809 def bgn_deftypefun(self, args): self.bgn_deftypefn('Function ' + args)
810 end_deftypefun = end_deftypefn
811
812 def bgn_deftypevr(self, args):
813 words = splitwords(args, 3)
814 [category, datatype, name], rest = words[:3], words[3:]
815 self.write('<DL><DT>')
816 self.expand('@code{' + datatype + '} @b{' + name + '}')
817 # If there are too many arguments, show them
818 for word in rest: self.expand(' ' + word)
819 self.expand(' -- ' + category)
820 self.write('<DD>\n')
821 self.index('fn', name)
822
823 end_deftypevr = end_deftypefn
824
825 def bgn_deftypevar(self, args):
826 self.bgn_deftypevr('Variable ' + args)
827 end_deftypevar = end_deftypevr
828
829 # --- Ditto for object-oriented languages ---
830
831 def bgn_defcv(self, args):
832 words = splitwords(args, 3)
833 [category, classname, name], rest = words[:3], words[3:]
834 self.write('<DL><DT>')
835 self.expand('@b{' + name + '}')
836 # If there are too many arguments, show them
837 for word in rest: self.expand(' ' + word)
838 self.expand(' -- ' + category + ' of ' + classname)
839 self.write('<DD>\n')
840 self.index('vr', name + ' @r{of ' + classname + '}')
841
842 end_defcv = end_deftypevr
843
844 def bgn_defivar(self, args):
845 self.bgn_defcv('{Instance Variable} ' + args)
846 end_defivar = end_defcv
847
848 def bgn_defop(self, args):
849 self.write('<DL><DT>')
850 words = splitwords(args, 3)
851 [category, classname, name], rest = words[:3], words[3:]
852 self.expand('@b{' + name + '}')
853 for word in rest: self.expand(' ' + makevar(word))
854 self.expand(' -- ' + category + ' on ' + classname)
855 self.write('<DD>\n')
856 self.index('fn', name + ' @r{on ' + classname + '}')
857
858 end_defop = end_defcv
859
860 def bgn_defmethod(self, args):
861 self.bgn_defop('Method ' + args)
862 end_defmethod = end_defop
863
864 # --- Ditto for data types ---
865
866 def bgn_deftp(self, args):
867 self.write('<DL><DT>')
868 words = splitwords(args, 2)
869 [category, name], rest = words[:2], words[2:]
870 self.expand('@b{' + name + '}')
871 for word in rest: self.expand(' ' + word)
872 self.expand(' -- ' + category)
873 self.write('<DD>\n')
874 self.index('tp', name)
875
876 end_deftp = end_defcv
877
878 # --- Making Lists and Tables
879
880 def bgn_enumerate(self, args):
881 if not args: args = '1'
882 self.itemnumber = args
883 self.write('<UL>\n')
884 def end_enumerate(self):
885 self.itemnumber = None
886 self.write('</UL>\n')
887
888 def bgn_itemize(self, args):
889 self.itemarg = args
890 self.write('<UL>\n')
891 def end_itemize(self):
892 self.itemarg = None
893 self.write('</UL>\n')
894
895 def bgn_table(self, args):
896 self.itemarg = args
897 self.write('<DL>\n')
898 def end_table(self):
899 self.itemarg = None
900 self.write('</DL>\n')
901
902 def bgn_ftable(self, args):
903 self.itemindex = 'fn'
904 self.bgn_table(args)
905 def end_ftable(self):
906 self.itemindex = None
907 self.end_table()
908
909 def do_item(self, args):
910 if self.itemindex: self.index(self.itemindex, args)
911 if self.itemarg:
912 if self.itemarg[0] == '@' and self.itemarg[1:2] and \
913 self.itemarg[1] in string.letters:
914 args = self.itemarg + '{' + args + '}'
915 else:
916 # some other character, e.g. '-'
917 args = self.itemarg + ' ' + args
918 if self.itemnumber <> None:
919 args = self.itemnumber + '. ' + args
920 self.itemnumber = increment(self.itemnumber)
921 if self.stack and self.stack[-1] == 'table':
922 self.write('<DT>')
923 self.expand(args)
924 self.write('<DD>')
925 else:
926 self.write('<LI>')
927 self.expand(args)
928 self.write(' ')
929 do_itemx = do_item # XXX Should suppress leading blank line
930
931 # --- Enumerations, displays, quotations ---
932 # XXX Most of these should increase the indentation somehow
933
934 def bgn_quotation(self, args): self.write('<P>')
935 def end_quotation(self): self.write('<P>\n')
936
937 def bgn_example(self, args):
938 self.nofill = self.nofill + 1
939 self.write('<UL COMPACT><CODE>')
940 def end_example(self):
941 self.write('</CODE></UL>\n')
942 self.nofill = self.nofill - 1
943
944 bgn_lisp = bgn_example # Synonym when contents are executable lisp code
945 end_lisp = end_example
946
947 bgn_smallexample = bgn_example # XXX Should use smaller font
948 end_smallexample = end_example
949
950 bgn_smalllisp = bgn_lisp # Ditto
951 end_smalllisp = end_lisp
952
953 def bgn_display(self, args):
954 self.nofill = self.nofill + 1
955 self.write('<UL COMPACT>\n')
956 def end_display(self):
957 self.write('</UL>\n')
958 self.nofill = self.nofill - 1
959
960 def bgn_format(self, args):
961 self.nofill = self.nofill + 1
962 self.write('<UL COMPACT>\n')
963 def end_format(self):
964 self.write('</UL>\n')
965 self.nofill = self.nofill - 1
966
967 def do_exdent(self, args): self.expand(args + '\n')
968 # XXX Should really mess with indentation
969
970 def bgn_flushleft(self, args):
971 self.nofill = self.nofill + 1
972 self.write('<UL COMPACT>\n')
973 def end_flushleft(self):
974 self.write('</UL>\n')
975 self.nofill = self.nofill - 1
976
977 def bgn_flushright(self, args):
978 self.nofill = self.nofill + 1
979 self.write('<ADDRESS COMPACT>\n')
980 def end_flushright(self):
981 self.write('</ADDRESS>\n')
982 self.nofill = self.nofill - 1
983
984 def bgn_menu(self, args): self.write('<H2>Menu</H2><DL COMPACT>\n')
985 def end_menu(self): self.write('</DL>\n')
986
987 def bgn_cartouche(self, args): pass
988 def end_cartouche(self): pass
989
990 # --- Indices ---
991
992 def resetindex(self):
993 self.noncodeindices = ['cp']
994 self.indextitle = {}
995 self.indextitle['cp'] = 'Concept'
996 self.indextitle['fn'] = 'Function'
997 self.indextitle['ky'] = 'Keyword'
998 self.indextitle['pg'] = 'Program'
999 self.indextitle['tp'] = 'Type'
1000 self.indextitle['vr'] = 'Variable'
1001 #
1002 self.whichindex = {}
1003 for name in self.indextitle.keys():
1004 self.whichindex[name] = []
1005
1006 def user_index(self, name, args):
1007 if self.whichindex.has_key(name):
1008 self.index(name, args)
1009 else:
1010 print '*** No index named', `name`
1011
1012 def do_cindex(self, args): self.index('cp', args)
1013 def do_findex(self, args): self.index('fn', args)
1014 def do_kindex(self, args): self.index('ky', args)
1015 def do_pindex(self, args): self.index('pg', args)
1016 def do_tindex(self, args): self.index('tp', args)
1017 def do_vindex(self, args): self.index('vr', args)
1018
1019 def index(self, name, args):
1020 self.whichindex[name].append(args, self.nodename)
1021
1022 def do_synindex(self, args):
1023 words = string.split(args)
1024 if len(words) <> 2:
1025 print '*** bad @synindex', args
1026 return
1027 [old, new] = words
1028 if not self.whichindex.has_key(old) or \
1029 not self.whichindex.has_key(new):
1030 print '*** bad key(s) in @synindex', args
1031 return
1032 if old <> new and \
1033 self.whichindex[old] is not self.whichindex[new]:
1034 inew = self.whichindex[new]
1035 inew[len(inew):] = self.whichindex[old]
1036 self.whichindex[old] = inew
1037 do_syncodeindex = do_synindex # XXX Should use code font
1038
1039 def do_printindex(self, args):
1040 words = string.split(args)
1041 for name in words:
1042 if self.whichindex.has_key(name):
1043 self.prindex(name)
1044 else:
1045 print '*** No index named', `name`
1046
1047 def prindex(self, name):
1048 iscodeindex = (name not in self.noncodeindices)
1049 index = self.whichindex[name]
1050 if not index: return
1051 if self.debugging:
1052 print '--- Generating', self.indextitle[name], 'index'
1053 # The node already provides a title
1054 index1 = []
1055 junkprog = regex.compile('^\(@[a-z]+\)?{')
1056 for key, node in index:
1057 sortkey = string.lower(key)
1058 # Remove leading `@cmd{' from sort key
1059 # -- don't bother about the matching `}'
1060 oldsortkey = sortkey
1061 while 1:
1062 i = junkprog.match(sortkey)
1063 if i < 0: break
1064 sortkey = sortkey[i:]
1065 index1.append(sortkey, key, node)
1066 del index[:]
1067 index1.sort()
1068 self.write('<DL COMPACT>\n')
1069 for sortkey, key, node in index1:
1070 if self.debugging > 1: print key, ':', node
1071 self.write('<DT>')
1072 if iscodeindex: key = '@code{' + key + '}'
1073 self.expand(key)
1074 self.write('<DD><A HREF="', makefile(node), \
1075 '">', node, '</A>\n')
1076 self.write('</DL>\n')
1077
1078 # --- Final error reports ---
1079
1080 def report(self):
1081 if self.unknown:
1082 print '--- Unrecognized commands ---'
1083 cmds = self.unknown.keys()
1084 cmds.sort()
1085 for cmd in cmds:
1086 print string.ljust(cmd, 20), self.unknown[cmd]
1087
1088
1089# Put @var{} around alphabetic substrings
1090def makevar(str):
1091 # XXX This breaks if str contains @word{...}
1092 return regsub.gsub('\([a-zA-Z_][a-zA-Z0-9_]*\)', '@var{\\1}', str)
1093
1094
1095# Split a string in "words" according to findwordend
1096def splitwords(str, minlength):
1097 words = []
1098 i = 0
1099 n = len(str)
1100 while i < n:
1101 while i < n and str[i] in ' \t\n': i = i+1
1102 if i >= n: break
1103 start = i
1104 i = findwordend(str, i, n)
1105 words.append(str[start:i])
1106 while len(words) < minlength: words.append('')
1107 return words
1108
1109
1110# Find the end of a "word", matching braces and interpreting @@ @{ @}
1111fwprog = regex.compile('[@{} ]')
1112def findwordend(str, i, n):
1113 level = 0
1114 while i < n:
1115 i = fwprog.search(str, i)
1116 if i < 0: break
1117 c = str[i]; i = i+1
1118 if c == '@': i = i+1 # Next character is not special
1119 elif c == '{': level = level+1
1120 elif c == '}': level = level-1
1121 elif c == ' ' and level <= 0: return i-1
1122 return n
1123
1124
1125# Convert a node name into a file name
1126def makefile(nodename):
1127 return string.lower(fixfunnychars(nodename)) + '.html'
1128
1129
1130# Characters that are perfectly safe in filenames and hyperlinks
1131goodchars = string.letters + string.digits + '!@-_=+.'
1132
1133# Replace characters that aren't perfectly safe by underscores
1134def fixfunnychars(addr):
1135 i = 0
1136 while i < len(addr):
1137 c = addr[i]
1138 if c not in goodchars:
1139 c = '_'
1140 addr = addr[:i] + c + addr[i+1:]
1141 i = i + len(c)
1142 return addr
1143
1144
1145# Increment a string used as an enumeration
1146def increment(s):
1147 if not s:
1148 return '1'
1149 for sequence in string.digits, string.lowercase, string.uppercase:
1150 lastc = s[-1]
1151 if lastc in sequence:
1152 i = string.index(sequence, lastc) + 1
1153 if i >= len(sequence):
1154 if len(s) == 1:
1155 s = sequence[0]*2
1156 if s == '00':
1157 s = '10'
1158 else:
1159 s = increment(s[:-1]) + sequence[0]
1160 else:
1161 s = s[:-1] + sequence[i]
1162 return s
1163 return s # Don't increment
1164
1165
1166def test():
1167 import sys
1168 parser = TexinfoParser()
1169 while sys.argv[1:2] == ['-d']:
1170 parser.debugging = parser.debugging + 1
1171 del sys.argv[1:2]
1172 if len(sys.argv) <> 3:
1173 print 'usage: texi2html [-d] [-d] inputfile outputdirectory'
1174 sys.exit(2)
1175 file = sys.argv[1]
1176 parser.setdirname(sys.argv[2])
1177 if file == '-':
1178 fp = sys.stdin
1179 else:
1180 parser.setincludedir(os.path.dirname(file))
1181 try:
1182 fp = open(file, 'r')
1183 except IOError, msg:
1184 print file, ':', msg
1185 sys.exit(1)
1186 parser.parse(fp)
1187 fp.close()
1188 parser.report()
1189
1190
1191test()