7 from optparse
import OptionParser
11 Normalizes whitespace in text.
13 >>> normalize_text("asd xxx")
15 >>> normalize_text(" asd\nxxx ")
18 return s
.replace("\n", " ").strip()
20 CXX_NAMESPACE_RE
= re
.compile(r
'[_a-zA-Z][_0-9a-zA-Z]*::')
21 def fix_definition(s
):
23 Removes C++ name qualifications from some definitions.
27 >>> fix_definition("bool flag")
29 >>> fix_definition("bool FooBar::flag")
31 >>> fix_definition("void(* _GeanyObjectClass::project_open) (GKeyFile *keyfile)")
32 'void(* project_open) (GKeyFile *keyfile)'
35 return CXX_NAMESPACE_RE
.sub(r
"", s
);
45 return "@%s %s" % (type, str)
53 def cb(self
, type, str):
55 words
= str.split(" ", 2);
56 #~ self.params.append(GtkDocParam.new(words[0], words[1].rstrip, self.annot))
58 elif (type == "return"):
59 #~ self.retval = GtkDocReturn.new(str.rstrip, self.annot)
61 elif (type == "since"):
62 self
.since
= str.rstrip()
63 elif (type == "geany:skip"):
64 self
.annot
.append("skip")
65 elif (type == "geany:nullable") or (type == "geany:skip"):
66 self
.annot
.append(type.split(":")[1])
67 elif (type == "geany:cb"):
68 self
.annot
.append("scope notified")
69 elif (type == "geany:cbdata"):
70 self
.annot
.append("closure")
71 elif (type == "geany:cbfree"):
72 self
.annot
.append("destroy")
73 elif (type == "geany:transfer") or (type == "geany:element-type") or (type == "geany:scope"):
74 type = type.split(":")[1]
75 self
.annot
.append("%s %s" % (type, str))
79 if (str != "NULL"): # FIXME: some of geany does @a NULL
89 def __init__(self
, cb
):
92 class DoxygenProcess(object):
96 # http://stackoverflow.com/questions/4624062/get-all-text-inside-a-tag-in-lxml
98 def stringify_children(node
):
99 from lxml
.etree
import tostring
100 from itertools
import chain
101 parts
= ([node
.text
] +
102 list(chain(*([c
.text
, tostring(c
).decode("utf-8"), c
.tail
] for c
in node
.getchildren()))) +
104 # filter removes possible Nones in texts and tails
105 return "".join(filter(None, parts
))
107 def get_program_listing(self
, xml
):
108 #~ return "--- CODE ---"
109 from lxml
.etree
import tostring
110 arr
= ["", "|[<!-- language=\"C\" -->"]
111 for l
in xml
.getchildren():
112 if (l
.tag
== "codeline"):
113 # a codeline is of the form
114 # <highlight class="normal">GeanyDocument<sp/>*doc<sp/>=<sp/>...;</highlight>
115 # <sp/> tags must be replaced with spaces, then just use the text
116 #~ html = self.stringify_children(l)
117 #~ print(etree.HTML(html))
118 h
= l
.find("highlight")
120 html
= tostring(h
).decode("utf-8")
121 html
= html
.replace("<sp/>", " ")
122 arr
.append(" " + tostring(etree
.HTML(html
), method
="text").decode("utf-8"))
124 return "\n".join(arr
)
126 def join_annot(self
):
127 s
= " ".join(map(lambda x
: "(%s)" % x
, self
.at
.annot
))
128 return s
+ ": " if s
else ""
130 def process_element(self
, xml
):
132 s
= self
.__process
_element
(xml
)
136 return self
.join_annot()
138 def get_return(self
):
139 return self
.at
.retval
144 def __process_element(self
, xml
):
149 for n
in xml
.getchildren():
150 if n
.tag
== "emphasis":
151 s
+= self
.at
.cb("a", self
.__process
_element
(n
))
152 if n
.tag
== "computeroutput":
153 s
+= self
.at
.cb("c", self
.__process
_element
(n
))
154 if n
.tag
== "itemizedlist":
155 s
+= "\n" + self
.__process
_element
(n
)
156 if n
.tag
== "listitem":
157 s
+= " - " + self
.__process
_element
(n
)
159 s
+= self
.__process
_element
(n
) + "\n"
161 s
+= n
.text
if n
.text
else ""
162 if n
.tag
== "simplesect":
163 ss
= self
.at
.cb(n
.get("kind"), self
.__process
_element
(n
))
164 s
+= ss
if ss
+ "\n" else ""
165 if n
.tag
== "programlisting":
166 s
+= self
.get_program_listing(n
)
167 if n
.tag
== "xrefsect":
168 s
+= self
.__process
_element
(n
)
169 if n
.tag
== "xreftitle":
170 s
+= self
.__process
_element
(n
) + ": "
171 if n
.tag
== "xrefdescription":
172 s
+= self
.__process
_element
(n
)
174 s
+= self
.__process
_element
(n
)
175 if n
.tag
== "linebreak":
179 # workaround for doxygen bug #646002
180 if n
.tag
== "htmlonly":
184 if n
.tag
.startswith("param"):
185 pass # parameters are handled separately in DoxyFunction::from_memberdef()
188 class DoxyMember(object):
189 def __init__(self
, name
, brief
, extra
= ""):
195 class DoxyElement(object):
197 def __init__(self
, name
, definition
, **kwargs
):
199 self
.definition
= definition
200 self
.brief
= kwargs
.get('brief', "")
201 self
.detail
= kwargs
.get('detail', "")
202 self
.members
= kwargs
.get('members', [])
203 self
.since
= kwargs
.get('since', "")
204 self
.extra
= kwargs
.get('extra', "")
205 self
.retval
= kwargs
.get('retval', None)
207 def is_documented(self
):
208 if (normalize_text(self
.brief
)) != "":
212 def add_brief(self
, xml
):
213 proc
= DoxygenProcess()
214 self
.brief
= proc
.process_element(xml
)
215 self
.extra
+= proc
.get_extra()
217 def add_detail(self
, xml
):
218 proc
= DoxygenProcess()
219 self
.detail
= proc
.process_element(xml
)
220 self
.extra
+= proc
.get_extra()
221 self
.since
= proc
.get_since()
223 def add_member(self
, xml
):
224 name
= xml
.find("name").text
225 proc
= DoxygenProcess()
226 brief
= proc
.process_element(xml
.find("briefdescription"))
227 # optional doxygen command output appears within <detaileddescription />
228 proc
.process_element(xml
.find("detaileddescription"))
229 self
.members
.append(DoxyMember(name
, normalize_text(brief
), proc
.get_extra()))
231 def add_param(self
, xml
):
232 name
= xml
.find("parameternamelist").find("parametername").text
233 proc
= DoxygenProcess()
234 brief
= proc
.process_element(xml
.find("parameterdescription"))
235 self
.members
.append(DoxyMember(name
, normalize_text(brief
), proc
.get_extra()))
237 def add_return(self
, xml
):
238 proc
= DoxygenProcess()
239 brief
= proc
.process_element(xml
)
240 self
.retval
= DoxyMember("ret", normalize_text(brief
), proc
.get_extra())
245 s
.append(" * %s: %s" % (self
.name
, self
.extra
))
246 for p
in self
.members
:
247 s
.append(" * @%s: %s %s" % (p
.name
, p
.extra
, p
.brief
))
249 s
.append(" * %s" % self
.brief
.replace("\n", "\n * "))
251 s
.append(" * %s" % self
.detail
.replace("\n", "\n * "))
254 s
.append(" * Returns: %s %s" % (self
.retval
.extra
, self
.retval
.brief
))
257 s
.append(" * Since: %s" % self
.since
)
262 class DoxyTypedef(DoxyElement
):
265 def from_memberdef(xml
):
266 name
= xml
.find("name").text
267 d
= normalize_text(xml
.find("definition").text
).replace("G_BEGIN_DECLS", "")
269 return DoxyTypedef(name
, d
)
271 class DoxyEnum(DoxyElement
):
274 def from_memberdef(xml
):
275 name
= xml
.find("name").text
276 d
= "typedef enum {\n"
277 for member
in xml
.findall("enumvalue"):
278 v
= member
.find("initializer")
279 d
+= "\t%s%s,\n" % ( member
.find("name").text
, " "+v
.text
if v
is not None else "")
280 d
+= "} %s;\n" % name
282 e
= DoxyEnum(name
, d
)
283 e
.add_brief(xml
.find("briefdescription"))
284 for p
in xml
.findall("enumvalue"):
288 class DoxyStruct(DoxyElement
):
291 def from_compounddef(xml
, typedefs
= []):
292 name
= xml
.find("compoundname").text
293 section
= xml
.find("sectiondef")
294 d
= "struct %s {\n" % name
;
295 for p
in section
.findall("memberdef"):
296 # workaround for struct members. g-ir-scanner can't properly map struct members
297 # (beginning with struct GeanyFoo) to the typedef and assigns a generic type for them
298 # thus we fix that up here and enforce usage of the typedef. These are written
299 # out first, before any struct definition, for this reason
300 # Exception: there are no typedefs for GeanyFooPrivate so skip those. Their exact
301 # type isn't needed anyway
302 s
= fix_definition(p
.find("definition").text
).lstrip()
304 if (words
[0] == "struct"):
305 if not (words
[1].endswith("Private") or words
[1].endswith("Private*")):
306 s
= " ".join(words
[1:])
310 e
= DoxyStruct(name
, d
)
311 e
.add_brief(xml
.find("briefdescription"))
312 for p
in section
.findall("memberdef"):
316 class DoxyFunction(DoxyElement
):
319 def from_memberdef(xml
):
320 name
= xml
.find("name").text
321 d
= normalize_text(xml
.find("definition").text
.replace("G_BEGIN_DECLS", ""))
322 d
+= " " + xml
.find("argsstring").text
+ ";"
323 d
= normalize_text(d
.replace("GEANY_API_SYMBOL", ""))
325 e
= DoxyFunction(name
, d
)
326 e
.add_brief(xml
.find("briefdescription"))
327 e
.add_detail(xml
.find("detaileddescription"))
328 for p
in xml
.xpath(".//detaileddescription/*/parameterlist[@kind='param']/parameteritem"):
330 x
= xml
.xpath(".//detaileddescription/*/simplesect[@kind='return']")
341 parser
= OptionParser(usage
="usage: %prog [options] XML_DIR")
342 parser
.add_option("--xmldir", metavar
="DIRECTORY", help="Path to Doxygen-generated XML files",
343 action
="store", dest
="xml_dir")
344 parser
.add_option("-d", "--outdir", metavar
="DIRECTORY", help="Path to Doxygen-generated XML files",
345 action
="store", dest
="outdir", default
=".")
346 parser
.add_option("-o", "--output", metavar
="FILE", help="Write output to FILE",
347 action
="store", dest
="outfile")
348 parser
.add_option("--sci-output", metavar
="FILE", help="Write scintilla_object_* output to FILE",
349 action
="store", dest
="scioutfile")
350 opts
, args
= parser
.parse_args(args
[1:])
354 outfile
= open(opts
.outfile
, "w+")
358 if (opts
.scioutfile
):
359 scioutfile
= open(opts
.scioutfile
, "w+")
363 if (outfile
is None):
364 sys
.stderr
.write("no output file\n")
367 if not (os
.path
.exists(xml_dir
)):
368 sys
.stderr
.write("invalid xml directory\n")
371 transform
= etree
.XSLT(etree
.parse(os
.path
.join(xml_dir
, "combine.xslt")))
372 doc
= etree
.parse(os
.path
.join(xml_dir
, "index.xml"))
373 root
= transform(doc
)
378 c_files
= root
.xpath(".//compounddef[@kind='file']/compoundname[substring(.,string-length(.)-1)='.c']/..")
379 h_files
= root
.xpath(".//compounddef[@kind='file']/compoundname[substring(.,string-length(.)-1)='.h']/..")
382 if not (f
.find("compoundname").text
.endswith("private.h")):
383 for n0
in f
.xpath(".//*/memberdef[@kind='typedef' and @prot='public']"):
384 if not (n0
.find("type").text
.replace("G_BEGIN_DECLS", "").lstrip().startswith("enum")):
385 e
= DoxyTypedef
.from_memberdef(n0
)
388 for n0
in f
.xpath(".//*/memberdef[@kind='enum' and @prot='public']"):
389 e
= DoxyEnum
.from_memberdef(n0
)
392 for n0
in root
.xpath(".//compounddef[@kind='struct' and @prot='public']"):
393 e
= DoxyStruct
.from_compounddef(n0
)
397 for n0
in f
.xpath(".//*/memberdef[@kind='function' and @prot='public']"):
398 e
= DoxyFunction
.from_memberdef(n0
)
401 outfile
.write("#include <glib.h>\n")
402 outfile
.write("#include <gtk/gtk.h>\n")
403 outfile
.write("typedef struct _ScintillaObject ScintillaObject;\n")
404 outfile
.write("typedef struct TMSourceFile TMSourceFile;\n")
405 outfile
.write("typedef struct TMWorkspace TMWorkspace;\n")
407 # write typedefs first, they are possibly undocumented but still required (even
408 # if they are documented, they must be written out without gtkdoc)
410 outfile
.write(e
.definition
)
411 outfile
.write("\n\n")
413 for e
in filter(lambda x
: x
.is_documented(), other
):
414 outfile
.write("\n\n")
415 outfile
.write(e
.to_gtkdoc())
416 outfile
.write(e
.definition
)
417 outfile
.write("\n\n")
418 if (e
.name
.startswith("sci_")):
419 scioutfile
.write(e
.to_gtkdoc().replace("sci_", "scintilla_object_"))
420 scioutfile
.write(e
.definition
.replace("sci_", "scintilla_object_"))
421 scioutfile
.write("\n\n")
425 if __name__
== "__main__":
426 sys
.exit(main(sys
.argv
))