]>
Commit | Line | Data |
---|---|---|
3313b612 MAL |
1 | #!/usr/bin/env python |
2 | # QAPI texi generator | |
3 | # | |
4 | # This work is licensed under the terms of the GNU LGPL, version 2+. | |
5 | # See the COPYING file in the top-level directory. | |
6 | """This script produces the documentation of a qapi schema in texinfo format""" | |
7 | import re | |
8 | import sys | |
9 | ||
10 | import qapi | |
11 | ||
597494ab | 12 | MSG_FMT = """ |
3313b612 MAL |
13 | @deftypefn {type} {{}} {name} |
14 | ||
15 | {body} | |
16 | ||
17 | @end deftypefn | |
18 | ||
19 | """.format | |
20 | ||
597494ab | 21 | TYPE_FMT = """ |
3313b612 MAL |
22 | @deftp {{{type}}} {name} |
23 | ||
24 | {body} | |
25 | ||
26 | @end deftp | |
27 | ||
28 | """.format | |
29 | ||
30 | EXAMPLE_FMT = """@example | |
31 | {code} | |
32 | @end example | |
33 | """.format | |
34 | ||
35 | ||
36 | def subst_strong(doc): | |
37 | """Replaces *foo* by @strong{foo}""" | |
38 | return re.sub(r'\*([^*\n]+)\*', r'@emph{\1}', doc) | |
39 | ||
40 | ||
41 | def subst_emph(doc): | |
42 | """Replaces _foo_ by @emph{foo}""" | |
43 | return re.sub(r'\b_([^_\n]+)_\b', r' @emph{\1} ', doc) | |
44 | ||
45 | ||
46 | def subst_vars(doc): | |
47 | """Replaces @var by @code{var}""" | |
48 | return re.sub(r'@([\w-]+)', r'@code{\1}', doc) | |
49 | ||
50 | ||
51 | def subst_braces(doc): | |
52 | """Replaces {} with @{ @}""" | |
53 | return doc.replace("{", "@{").replace("}", "@}") | |
54 | ||
55 | ||
56 | def texi_example(doc): | |
57 | """Format @example""" | |
58 | # TODO: Neglects to escape @ characters. | |
59 | # We should probably escape them in subst_braces(), and rename the | |
60 | # function to subst_special() or subs_texi_special(). If we do that, we | |
61 | # need to delay it until after subst_vars() in texi_format(). | |
62 | doc = subst_braces(doc).strip('\n') | |
63 | return EXAMPLE_FMT(code=doc) | |
64 | ||
65 | ||
66 | def texi_format(doc): | |
67 | """ | |
68 | Format documentation | |
69 | ||
70 | Lines starting with: | |
71 | - |: generates an @example | |
72 | - =: generates @section | |
73 | - ==: generates @subsection | |
74 | - 1. or 1): generates an @enumerate @item | |
75 | - */-: generates an @itemize list | |
76 | """ | |
77 | lines = [] | |
78 | doc = subst_braces(doc) | |
79 | doc = subst_vars(doc) | |
80 | doc = subst_emph(doc) | |
81 | doc = subst_strong(doc) | |
82 | inlist = "" | |
83 | lastempty = False | |
84 | for line in doc.split('\n'): | |
85 | empty = line == "" | |
86 | ||
87 | # FIXME: Doing this in a single if / elif chain is | |
88 | # problematic. For instance, a line without markup terminates | |
89 | # a list if it follows a blank line (reaches the final elif), | |
90 | # but a line with some *other* markup, such as a = title | |
91 | # doesn't. | |
92 | # | |
93 | # Make sure to update section "Documentation markup" in | |
94 | # docs/qapi-code-gen.txt when fixing this. | |
95 | if line.startswith("| "): | |
96 | line = EXAMPLE_FMT(code=line[2:]) | |
97 | elif line.startswith("= "): | |
98 | line = "@section " + line[2:] | |
99 | elif line.startswith("== "): | |
100 | line = "@subsection " + line[3:] | |
101 | elif re.match(r'^([0-9]*\.) ', line): | |
102 | if not inlist: | |
103 | lines.append("@enumerate") | |
104 | inlist = "enumerate" | |
105 | line = line[line.find(" ")+1:] | |
106 | lines.append("@item") | |
107 | elif re.match(r'^[*-] ', line): | |
108 | if not inlist: | |
109 | lines.append("@itemize %s" % {'*': "@bullet", | |
110 | '-': "@minus"}[line[0]]) | |
111 | inlist = "itemize" | |
112 | lines.append("@item") | |
113 | line = line[2:] | |
114 | elif lastempty and inlist: | |
115 | lines.append("@end %s\n" % inlist) | |
116 | inlist = "" | |
117 | ||
118 | lastempty = empty | |
119 | lines.append(line) | |
120 | ||
121 | if inlist: | |
122 | lines.append("@end %s\n" % inlist) | |
123 | return "\n".join(lines) | |
124 | ||
125 | ||
126 | def texi_body(doc): | |
127 | """ | |
128 | Format the body of a symbol documentation: | |
129 | - main body | |
130 | - table of arguments | |
131 | - followed by "Returns/Notes/Since/Example" sections | |
132 | """ | |
133 | body = texi_format(str(doc.body)) + "\n" | |
134 | if doc.args: | |
135 | body += "@table @asis\n" | |
136 | for arg, section in doc.args.iteritems(): | |
137 | desc = str(section) | |
138 | opt = '' | |
b116fd8e | 139 | if section.optional: |
42bebcc1 MA |
140 | desc = re.sub(r'^ *#optional *\n?|\n? *#optional *$|#optional', |
141 | '', desc) | |
3313b612 MAL |
142 | opt = ' (optional)' |
143 | body += "@item @code{'%s'}%s\n%s\n" % (arg, opt, | |
144 | texi_format(desc)) | |
145 | body += "@end table\n" | |
146 | ||
147 | for section in doc.sections: | |
148 | name, doc = (section.name, str(section)) | |
149 | func = texi_format | |
150 | if name.startswith("Example"): | |
151 | func = texi_example | |
152 | ||
153 | if name: | |
1ede77df MAL |
154 | # prefer @b over @strong, so txt doesn't translate it to *Foo:* |
155 | body += "\n\n@b{%s:}\n" % name | |
156 | ||
157 | body += func(doc) | |
3313b612 MAL |
158 | |
159 | return body | |
160 | ||
161 | ||
162 | def texi_alternate(expr, doc): | |
163 | """Format an alternate to texi""" | |
164 | body = texi_body(doc) | |
597494ab MAL |
165 | return TYPE_FMT(type="Alternate", |
166 | name=doc.symbol, | |
167 | body=body) | |
3313b612 MAL |
168 | |
169 | ||
170 | def texi_union(expr, doc): | |
171 | """Format a union to texi""" | |
172 | discriminator = expr.get("discriminator") | |
173 | if discriminator: | |
174 | union = "Flat Union" | |
175 | else: | |
176 | union = "Simple Union" | |
177 | ||
178 | body = texi_body(doc) | |
597494ab MAL |
179 | return TYPE_FMT(type=union, |
180 | name=doc.symbol, | |
181 | body=body) | |
3313b612 MAL |
182 | |
183 | ||
184 | def texi_enum(expr, doc): | |
185 | """Format an enum to texi""" | |
186 | for i in expr['data']: | |
187 | if i not in doc.args: | |
b116fd8e | 188 | doc.args[i] = qapi.QAPIDoc.ArgSection(i) |
3313b612 | 189 | body = texi_body(doc) |
597494ab MAL |
190 | return TYPE_FMT(type="Enum", |
191 | name=doc.symbol, | |
3313b612 MAL |
192 | body=body) |
193 | ||
194 | ||
195 | def texi_struct(expr, doc): | |
196 | """Format a struct to texi""" | |
197 | body = texi_body(doc) | |
597494ab MAL |
198 | return TYPE_FMT(type="Struct", |
199 | name=doc.symbol, | |
200 | body=body) | |
3313b612 MAL |
201 | |
202 | ||
203 | def texi_command(expr, doc): | |
204 | """Format a command to texi""" | |
205 | body = texi_body(doc) | |
597494ab MAL |
206 | return MSG_FMT(type="Command", |
207 | name=doc.symbol, | |
208 | body=body) | |
3313b612 MAL |
209 | |
210 | ||
211 | def texi_event(expr, doc): | |
212 | """Format an event to texi""" | |
213 | body = texi_body(doc) | |
597494ab MAL |
214 | return MSG_FMT(type="Event", |
215 | name=doc.symbol, | |
216 | body=body) | |
3313b612 MAL |
217 | |
218 | ||
219 | def texi_expr(expr, doc): | |
220 | """Format an expr to texi""" | |
221 | (kind, _) = expr.items()[0] | |
222 | ||
223 | fmt = {"command": texi_command, | |
224 | "struct": texi_struct, | |
225 | "enum": texi_enum, | |
226 | "union": texi_union, | |
227 | "alternate": texi_alternate, | |
228 | "event": texi_event}[kind] | |
229 | ||
230 | return fmt(expr, doc) | |
231 | ||
232 | ||
233 | def texi(docs): | |
234 | """Convert QAPI schema expressions to texi documentation""" | |
235 | res = [] | |
236 | for doc in docs: | |
237 | expr = doc.expr | |
238 | if not expr: | |
239 | res.append(texi_body(doc)) | |
240 | continue | |
241 | try: | |
242 | doc = texi_expr(expr, doc) | |
243 | res.append(doc) | |
244 | except: | |
245 | print >>sys.stderr, "error at @%s" % doc.info | |
246 | raise | |
247 | ||
248 | return '\n'.join(res) | |
249 | ||
250 | ||
251 | def main(argv): | |
252 | """Takes schema argument, prints result to stdout""" | |
253 | if len(argv) != 2: | |
254 | print >>sys.stderr, "%s: need exactly 1 argument: SCHEMA" % argv[0] | |
255 | sys.exit(1) | |
256 | ||
257 | schema = qapi.QAPISchema(argv[1]) | |
bc52d03f MA |
258 | if not qapi.doc_required: |
259 | print >>sys.stderr, ("%s: need pragma 'doc-required' " | |
260 | "to generate documentation" % argv[0]) | |
3313b612 MAL |
261 | print texi(schema.docs) |
262 | ||
263 | ||
264 | if __name__ == "__main__": | |
265 | main(sys.argv) |