Rename new "buildhtml.py" setting "rst_source" to "source".
[docutils.git] / sandbox / blais / elisp-reader / elisp2rst
blobb4c2a82a8bd3565f7fc42b3db80fa6391588ce4f
1 #!/usr/bin/env python
3 """elisp2rst [<options>] <file.el>
5 Emacs-LISP to reStructuredText converter.
6 """
8 import sys, re, os
13 hierarchy = ('=', '-', '~', '+', '^')
15 def underline(title, char, outfile):
16 """
17 Outputs 'title' with underline character 'char' to file 'outfile'
18 """
19 print >> outfile, title
20 print >> outfile, char * len(title)
22 def printpar(paragraph, outfile, indent=None):
23 """
24 Output a paragraph's lines to 'outfile'.
25 """
26 write = outfile.write
27 write(os.linesep)
28 for line in map(str.rstrip, paragraph):
29 if indent:
30 write(indent)
31 write(line)
32 write(os.linesep)
35 class ElispFormatError(Exception):
36 """
37 Error raised during the parsing of Emacs-LISP code.
38 """
41 def elisp2rst(infile, outfile):
42 """
43 Reads Emacs-LISP text and converts it in to reStructuredText. 'infile' and
44 'outfile' are expected to be open file objects for input and output.
46 This code does the following transformations:
48 FIXME TODO:
49 - create a title and subtitle
50 - converts the RFC822 headers into bibliographic fields
51 - converts the copyright line at the top into a biblio field
52 - places the copyright notice in a bibliographic field
53 - converts the ;;; section titles to include underlines
54 - adds an extra colon at the end of appropriate lines for creating literal
55 blocks
57 """
58 hidx = 0 # index in the hierarchy
60 # Get the title and subtitle lines.
61 titleline = infile.next()
62 mo = re.match('^;;; (.*) --- (.*)$', titleline)
63 if mo:
64 title, subtitle = mo.group(1, 2)
65 else:
66 title, subtitle = titleline[4:], None
67 if title:
68 underline(title, hierarchy[hidx], outfile)
69 hidx += 1
70 if subtitle:
71 underline(subtitle, hierarchy[hidx], outfile)
72 hidx += 1
74 # Process the rest of the lines until we hit a ';;; Code:' section.
75 precommentary, done_pre = [], False
76 "List of paragraphs that appear before the commentary."
78 copyright_years, fields = None, None
79 "Copyright years and RFC822 field list, if present in the pre-commentary."
81 parnum = 0
82 paragraph = []
83 for line in infile:
84 # Section title.
85 mo = re.match(';;; (.*)', line)
86 if mo:
87 title = mo.group(1)
88 # If we reached the code marker, stop processing.
89 if title.startswith('Code:'):
90 break
92 if title.startswith('Commentary:'):
93 done_pre = True
95 # Output the pre-commentary stuff.
96 if fields:
97 for line in fields:
98 if line.startswith(' ') or line.startswith('\t'):
99 print >> outfile, ' %s' % line
100 else:
101 print >> outfile, ':%s' % line
102 outfile.write(os.linesep)
104 prepars = []
105 if copyright_years:
106 prepars.append(copyright_years)
107 prepars.extend(precommentary)
108 if prepars:
109 print >> outfile, '.. note::'
110 print >> outfile
111 for par in prepars:
112 printpar(par, outfile, indent=' ')
115 if title.endswith(':'):
116 title = title[:-1]
118 outfile.write('\n')
119 underline(title, hierarchy[hidx], outfile)
120 continue
122 # Detect LISP code and exit if we find some.
123 if re.match('\([a-zA-Z-]+( |$)', line):
124 break
126 # Normal text contents.
127 mo = re.match(';;? ?(.*)$', line)
128 if mo:
129 line_contents = mo.group(1)
130 line_contents = re.sub("`(.*?)'", "``\\1``", line_contents)
131 paragraph.append(line_contents)
132 else:
133 if not re.match('^[ \t\f]*$', line):
134 raise ElispFormatError("Unknown line format")
135 elif paragraph:
136 if not done_pre:
137 if parnum == 0 and paragraph[0].startswith('Copyright'):
138 copyright_years = paragraph
139 elif parnum == 1 and re.match('[A-Za-z]+: ', paragraph[0]):
140 fields = paragraph
141 else:
142 precommentary.append(paragraph)
143 else:
144 printpar(paragraph, outfile)
146 paragraph = []
147 parnum += 1
150 if paragraph:
151 printpar(paragraph, outfile)
152 paragraph = []
153 parnum += 1
161 def main():
162 import optparse
163 parser = optparse.OptionParser(__doc__.strip())
164 opts, args = parser.parse_args()
166 if len(args) != 1:
167 parser.error("You must specify a single Emacs-LISP file as input.")
168 elispfn = args[0]
170 elispf = open(elispfn, 'r')
172 elisp2rst(elispf, sys.stdout)
179 if __name__ == '__main__':
180 main()