1 #!/usr/bin/env python3
2 #
3 # Indexes the examples and build an XML description
4 #
5 import glob
6 import sys
7 try:
8 import libxml2
9 except:
10 print("libxml2 python bindings not available")
11 sys.exit(1)
12 sys.path.insert(0, "..")
13 from apibuild import CParser, escape
14
15 examples = []
16 extras = ['examples.xsl', 'index.html', 'index.py']
17 tests = []
18 sections = {}
19 symbols = {}
20 api_dict = None
21 api_doc = None
22
23 def load_api():
24 global api_dict
25 global api_doc
26
27 if api_dict != None:
28 return
29 api_dict = {}
30 try:
31 print("loading ../libxml2-api.xml")
32 api_doc = libxml2.parseFile("../libxml2-api.xml")
33 except:
34 print("failed to parse ../libxml2-api.xml")
35 sys.exit(1)
36
37 def find_symbol(name):
38 global api_dict
39 global api_doc
40
41 if api_doc == None:
42 load_api()
43
44 if name == None:
45 return
46 if name in api_dict:
47 return api_dict[name]
48 ctxt = api_doc.xpathNewContext()
49 res = ctxt.xpathEval("/api/symbols/*[@name = '%s']" % (name))
50 if type(res) == type([]) and len(res) >= 1:
51 if len(res) > 1:
52 print("Found %d references to %s in the API" % (len(res), name))
53 node = res[0]
54 typ = node.name
55 file = node.xpathEval("string(@file)")
56 info = node.xpathEval("string(info)")
57 else:
58 print("Reference %s not found in the API" % (name))
59 return None
60 ret = (typ, file, info)
61 api_dict[name] = ret
62 return ret
63
64 def parse_top_comment(filename, comment):
65 res = {}
66 lines = comment.split("\n")
67 item = None
68 for line in lines:
69 while line != "" and (line[0] == ' ' or line[0] == '\t'):
70 line = line[1:]
71 while line != "" and line[0] == '*':
72 line = line[1:]
73 while line != "" and (line[0] == ' ' or line[0] == '\t'):
74 line = line[1:]
75 try:
76 (it, line) = line.split(":", 1)
77 item = it
78 while line != "" and (line[0] == ' ' or line[0] == '\t'):
79 line = line[1:]
80 if item in res:
81 res[item] = res[item] + " " + line
82 else:
83 res[item] = line
84 except:
85 if item != None:
86 if item in res:
87 res[item] = res[item] + " " + line
88 else:
89 res[item] = line
90 return res
91
92 def parse(filename, output):
93 global symbols
94 global sections
95
96 parser = CParser(filename)
97 parser.collect_references()
98 idx = parser.parse()
99 info = parse_top_comment(filename, parser.top_comment)
100 output.write(" <example filename='%s'>\n" % filename)
101 try:
102 synopsis = info['synopsis']
103 output.write(" <synopsis>%s</synopsis>\n" % escape(synopsis));
104 except:
105 print("Example %s lacks a synopsis description" % (filename))
106 try:
107 purpose = info['purpose']
108 output.write(" <purpose>%s</purpose>\n" % escape(purpose));
109 except:
110 print("Example %s lacks a purpose description" % (filename))
111 try:
112 usage = info['usage']
113 output.write(" <usage>%s</usage>\n" % escape(usage));
114 except:
115 print("Example %s lacks an usage description" % (filename))
116 try:
117 test = info['test']
118 output.write(" <test>%s</test>\n" % escape(test));
119 progname=filename[0:-2]
120 command=test.replace(progname, './' + progname, 1)
121 tests.append(command)
122 except:
123 pass
124 try:
125 author = info['author']
126 output.write(" <author>%s</author>\n" % escape(author));
127 except:
128 print("Example %s lacks an author description" % (filename))
129 try:
130 copy = info['copy']
131 output.write(" <copy>%s</copy>\n" % escape(copy));
132 except:
133 print("Example %s lacks a copyright description" % (filename))
134 try:
135 section = info['section']
136 output.write(" <section>%s</section>\n" % escape(section));
137 if section in sections:
138 sections[section].append(filename)
139 else:
140 sections[section] = [filename]
141 except:
142 print("Example %s lacks a section description" % (filename))
143 for topic in sorted(info.keys()):
144 if topic != "purpose" and topic != "usage" and \
145 topic != "author" and topic != "copy" and \
146 topic != "section" and topic != "synopsis" and topic != "test":
147 str = info[topic]
148 output.write(" <extra topic='%s'>%s</extra>\n" % (
149 escape(topic), escape(str)))
150 output.write(" <includes>\n")
151 for include in sorted(idx.includes.keys()):
152 if include.find("libxml") != -1:
153 id = idx.includes[include]
154 line = id.get_lineno()
155 output.write(" <include line='%d'>%s</include>\n" %
156 (line, escape(include)))
157 output.write(" </includes>\n")
158 output.write(" <uses>\n")
159 for ref in sorted(idx.references.keys()):
160 id = idx.references[ref]
161 name = id.get_name()
162 line = id.get_lineno()
163 if name in symbols:
164 sinfo = symbols[name]
165 refs = sinfo[0]
166 # gather at most 5 references per symbols
167 if refs > 5:
168 continue
169 sinfo.append(filename)
170 sinfo[0] = refs + 1
171 else:
172 symbols[name] = [1, filename]
173 info = find_symbol(name)
174 if info != None:
175 type = info[0]
176 file = info[1]
177 output.write(" <%s line='%d' file='%s' name='%s'/>\n" % (type,
178 line, file, name))
179 else:
180 type = id.get_type()
181 output.write(" <%s line='%d' name='%s'/>\n" % (type,
182 line, name))
183
184 output.write(" </uses>\n")
185 output.write(" </example>\n")
186
187 return idx
188
189 def dump_symbols(output):
190 global symbols
191
192 output.write(" <symbols>\n")
193 for symbol in sorted(symbols.keys()):
194 output.write(" <symbol name='%s'>\n" % (symbol))
195 info = symbols[symbol]
196 i = 1
197 while i < len(info):
198 output.write(" <ref filename='%s'/>\n" % (info[i]))
199 i = i + 1
200 output.write(" </symbol>\n")
201 output.write(" </symbols>\n")
202
203 def dump_sections(output):
204 global sections
205
206 output.write(" <sections>\n")
207 for section in sorted(sections.keys()):
208 output.write(" <section name='%s'>\n" % (section))
209 info = sections[section]
210 i = 0
211 while i < len(info):
212 output.write(" <example filename='%s'/>\n" % (info[i]))
213 i = i + 1
214 output.write(" </section>\n")
215 output.write(" </sections>\n")
216
217 def dump_Makefile():
218 for file in glob.glob('*.xml'):
219 extras.append(file)
220 for file in glob.glob('*.res'):
221 extras.append(file)
222 Makefile="""##
223 ## This file is auto-generated by index.py
224 ## DO NOT EDIT !!!
225 ##
226
227 AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include
228 LDADD = $(top_builddir)/libxml2.la
229
230 CLEANFILES = *.tmp
231
232 rebuild:
233 \tcd $(srcdir) && $(PYTHON) index.py
234 \t$(MAKE) Makefile
235 \tcd $(srcdir) && xsltproc examples.xsl examples.xml
236 \t-cd $(srcdir) && xmllint --valid --noout index.html
237
238 .PHONY: rebuild
239
240 install-data-local:
241 \t$(MKDIR_P) $(DESTDIR)$(docdir)/examples
242 \t-$(INSTALL) -m 0644 $(srcdir)/*.html $(srcdir)/*.c $(DESTDIR)$(docdir)/examples/
243
244 clean-local:
245 \ttest -f Makefile.am || rm -f test?.xml
246
247 """
248 examples.sort()
249 extras.sort()
250 tests.sort()
251 EXTRA_DIST=""
252 for extra in extras:
253 EXTRA_DIST = EXTRA_DIST + " \\\n\t" + extra
254 Makefile = Makefile + "EXTRA_DIST =%s\n\n" % (EXTRA_DIST)
255 check_PROGRAMS=""
256 for example in examples:
257 check_PROGRAMS = check_PROGRAMS + " \\\n\t" + example
258 Makefile = Makefile + "check_PROGRAMS =%s\n\n" % (check_PROGRAMS)
259 for example in examples:
260 Makefile = Makefile + "%s_SOURCES = %s.c\n\n" % (example, example)
261 Makefile = Makefile + "valgrind: \n\t$(MAKE) CHECKER='valgrind' tests\n\n"
262 Makefile = Makefile + "tests: $(check_PROGRAMS)\n"
263 Makefile = Makefile + "\t@test -f Makefile.am || test -f test1.xml || $(LN_S) $(srcdir)/test?.xml .\n"
264 Makefile = Makefile + "\t@(echo '## examples regression tests')\n"
265 for test in tests:
266 Makefile = Makefile + "\t@$(CHECKER) %s\n" % (test)
267 Makefile = Makefile + "\t@rm *.tmp\n"
268 try:
269 old = open("Makefile.am", "r").read()
270 if old != Makefile:
271 n = open("Makefile.am", "w").write(Makefile)
272 print("Updated Makefile.am")
273 except:
274 print("Failed to read or save Makefile.am")
275
276 if __name__ == "__main__":
277 load_api()
278 output = open("examples.xml", "w")
279 output.write("<examples>\n")
280
281 for file in sorted(glob.glob('*.c')):
282 parse(file, output)
283 examples.append(file[:-2])
284
285 dump_symbols(output)
286 dump_sections(output)
287 output.write("</examples>\n")
288 output.close()
289 #dump_Makefile()
290