Branch: refs/heads/master Author: Thomas Martitz kugel@rockbox.org Committer: Thomas Martitz kugel@rockbox.org Date: Sun, 07 Feb 2016 16:50:23 UTC Commit: 658821d2dd89feb347f860dd3831b569b7a40164 https://github.com/geany/geany/commit/658821d2dd89feb347f860dd3831b569b7a401...
Log Message: ----------- api: add script to generate {geany,geany-scintilla}-gtkdoc.h
The script reads the doxygen xml output and generates two headers (optionally a single header) that contains all of the plugin API in gtk-doc format.
Two headers because it's preferrable to group Scintilla related stuff into its own namespace. This is a lot easier if g-ir-scanner can work with a separate header file. If we change minds later on the script is prepared to generate only one header.
Modified Paths: -------------- configure.ac doc/Doxyfile.in doc/Makefile.am m4/geany-docutils.m4 scripts/gen-api-gtkdoc.py
Modified: configure.ac 1 lines changed, 1 insertions(+), 0 deletions(-) =================================================================== @@ -123,6 +123,7 @@ AC_SUBST([pkgdatadir]) # Documentation tools GEANY_CHECK_DOCUTILS GEANY_CHECK_DOXYGEN +GEANY_CHECK_PYTHON
# libgeany GEANY_LIB_INIT
Modified: doc/Doxyfile.in 3 lines changed, 2 insertions(+), 1 deletions(-) =================================================================== @@ -845,7 +845,8 @@ RECURSIVE = NO # Note that relative paths are relative to the directory from which doxygen is # run.
-EXCLUDE = +EXCLUDE = @top_srcdir@/doc/geany-gtkdoc.h \ + @top_srcdir@/doc/geany-scintilla-gtkdoc.h
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded
Modified: doc/Makefile.am 24 lines changed, 22 insertions(+), 2 deletions(-) =================================================================== @@ -104,19 +104,39 @@ doxygen_sources = \ # for xml output Doxyfile-gi: Doxyfile $(AM_V_GEN)$(SED) \ + -e 's,addtogir=@internal,addtogir=,' \ -e 's,^(GENERATE_HTML.*)YES,\1NO,' \ -e 's,^(GENERATE_XML.*)NO,\1YES,' \ -e 's,^(WARN_IF_UNDOCUMENTED.*)YES,\1NO,' \ + -e 's,^(SORT_MEMBER_DOCS.*)YES,\1NO,' \ + -e 's,^(SORT_BRIEF_DOCS.*)YES,\1NO,' \ $< > $@ || { $(RM) $@ && exit 1; }
Doxyfile.stamp: Doxyfile Doxyfile-gi $(doxygen_sources) $(AM_V_GEN)$(DOXYGEN) Doxyfile-gi && $(DOXYGEN) Doxyfile && echo "" > $@
-all-local: Doxyfile.stamp +ALL_TARGETS = Doxyfile.stamp + +if WITH_PYTHON + +geany-scintilla-gtkdoc.h: geany-gtkdoc.h + +geany-gtkdoc.h: Doxyfile.stamp $(top_srcdir)/scripts/gen-api-gtkdoc.py + $(AM_V_GEN)$(top_srcdir)/scripts/gen-api-gtkdoc.py xml -d $(builddir) \ + -o geany-gtkdoc.h --sci-output geany-scintilla-gtkdoc.h + +ALL_TARGETS += geany-gtkdoc.h geany-scintilla-gtkdoc.h + +geany_includedir = $(includedir)/geany/gtkdoc +geany_include_HEADERS = geany-gtkdoc.h geany-scintilla-gtkdoc.h + +endif + +all-local: $(ALL_TARGETS)
clean-local: clean-api-docs-local clean-api-docs-local: - -rm -rf reference/ xml/ Doxyfile.stamp doxygen_* + -rm -rf reference/ xml/ doxygen_* $(ALL_TARGETS)
endif
Modified: m4/geany-docutils.m4 13 lines changed, 13 insertions(+), 0 deletions(-) =================================================================== @@ -70,3 +70,16 @@ AC_DEFUN([GEANY_CHECK_DOCUTILS_PDF], AM_CONDITIONAL([WITH_RST2PDF], [test "x$geany_enable_pdf_docs" != "xno"]) GEANY_STATUS_ADD([Build PDF documentation], [$geany_enable_pdf_docs]) ]) + +dnl +dnl GEANY_CHECK_PYTHON +dnl For gtkdoc header generation +dnl +AC_DEFUN([GEANY_CHECK_PYTHON], +[ + AM_PATH_PYTHON([2.7], [], []) + + AM_CONDITIONAL([WITH_PYTHON], [test -n "$PYTHON"]) + AM_COND_IF([WITH_PYTHON], + [GEANY_STATUS_ADD([Using Python version], [$PYTHON_VERSION])]) +])
Modified: scripts/gen-api-gtkdoc.py 411 lines changed, 411 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,411 @@ +#!/usr/bin/env python + +import os +import sys +import re +from lxml import etree +from optparse import OptionParser + +xml_dir = None +outfile = None +scioutfile = None + +parser = OptionParser(usage="usage: %prog [options] XML_DIR") +parser.add_option("--xmldir", metavar="DIRECTORY", help="Path to Doxygen-generated XML files", + action="store", dest="xml_dir") +parser.add_option("-d", "--outdir", metavar="DIRECTORY", help="Path to Doxygen-generated XML files", + action="store", dest="outdir", default=".") +parser.add_option("-o", "--output", metavar="FILE", help="Write output to FILE", + action="store", dest="outfile") +parser.add_option("--sci-output", metavar="FILE", help="Write scintilla_object_* output to FILE", + action="store", dest="scioutfile") +(opts,args) = parser.parse_args() + +xml_dir = args[0] +if (opts.outfile): + outfile = open(opts.outfile, "w+") +else: + outfile=sys.stdout + +if (opts.scioutfile): + scioutfile = open(opts.scioutfile, "w+") +else: + scioutfile = outfile + +if (outfile is None): + sys.stderr.write("no output file\n") + exit(1) + +if not (os.path.exists(xml_dir)): + sys.stderr.write("invalid xml directory\n") + exit(1) + + +# " asd\nxxx " => "asd xxx" +def normalize_text(s): + return s.replace("\n", " ").lstrip().rstrip() + +assert(normalize_text("asd xxx") == "asd xxx") +assert(normalize_text(" asd\nxxx ") == "asd xxx") + +# doxygen records some definitions in C++ style, fix those +# "bool FooBar::flag" => "bool flag" +# void(* _GeanyObjectClass::project_open) (GKeyFile *keyfile) => void(* project_open) (GKeyFile *keyfile) +prog = re.compile(r'[_a-zA-Z][_0-9a-zA-Z]*::') +def fix_definition(s): + return prog.sub(r"", s); + +assert(fix_definition("bool flag") == "bool flag") +assert(fix_definition("bool FooBar::flag") == "bool flag") +assert(fix_definition("void(* _GeanyObjectClass::project_open) (GKeyFile *keyfile)") == "void(* project_open) (GKeyFile *keyfile)") + +transform = etree.XSLT(etree.parse(os.path.join(xml_dir, "combine.xslt"))) +doc = etree.parse(os.path.join(xml_dir, "index.xml")) +root = transform(doc) + +class AtAt(object): + + def __init__(self): + self.retval = None + self.since = "" + self.annot = [] + + def cb(type, str): + return "@%s %s" % (type, str) + +class AtDoc(object): + def __init__(self): + self.retval = None + self.since = "" + self.annot = [] + + def cb(self, type, str): + if (type == "param"): + words = str.split(" ", 2); + #~ self.params.append(GtkDocParam.new(words[0], words[1].rstrip, self.annot)) + self.annot = [] + elif (type == "return"): + #~ self.retval = GtkDocReturn.new(str.rstrip, self.annot) + self.annot = [] + elif (type == "since"): + self.since = str.rstrip() + elif (type == "geany:skip"): + self.annot.append("skip") + elif (type == "geany:nullable") or (type == "geany:skip"): + self.annot.append(type.split(":")[1]) + elif (type == "geany:cb"): + self.annot.append("scope notified") + elif (type == "geany:cbdata"): + self.annot.append("closure") + elif (type == "geany:cbfree"): + self.annot.append("destroy") + elif (type == "geany:transfer") or (type == "geany:element-type") or (type == "geany:scope"): + type = type.split(":")[1] + self.annot.append("%s %s" % (type, str)) + elif (type == "see"): + return "See " + str + elif (type == "a"): + if (str != "NULL"): # FIXME: some of geany does @a NULL + return "@" + str + else: + return str + else: + return str + + return "" + +class At(object): + def __init__(self, cb): + self.cb = cb + +class DoxygenProcess(object): + def __init__(self): + self.at = None + + # http://stackoverflow.com/questions/4624062/get-all-text-inside-a-tag-in-lxml + @staticmethod + def stringify_children(node): + from lxml.etree import tostring + from itertools import chain + parts = ([node.text] + + list(chain(*([c.text, tostring(c).decode("utf-8"), c.tail] for c in node.getchildren()))) + + [node.tail]) + # filter removes possible Nones in texts and tails + return "".join(filter(None, parts)) + + def get_program_listing(self, xml): + #~ return "--- CODE ---" + from lxml.etree import tostring + arr = ["", "|[<!-- language=\"C\" -->"] + for l in xml.getchildren(): + if (l.tag == "codeline"): + # a codeline is of the form + # <highlight class="normal">GeanyDocument<sp/>*doc<sp/>=<sp/>...;</highlight> + # <sp/> tags must be replaced with spaces, then just use the text + #~ html = self.stringify_children(l) + #~ print(etree.HTML(html)) + h = l.find("highlight") + if h is not None: + html = tostring(h).decode("utf-8") + html = html.replace("<sp/>", " ") + arr.append(" " + tostring(etree.HTML(html), method="text").decode("utf-8")) + arr.append("]|") + return "\n".join(arr) + + def join_annot(self): + s = " ".join(map(lambda x: "(%s)" % x, self.at.annot)) + return s + ": " if s else "" + + def process_element(self, xml): + self.at = AtDoc() + s = self.__process_element(xml) + return s + + def get_extra(self): + return self.join_annot() + + def get_return(self): + return self.at.retval + + def get_since(self): + return self.at.since + + def __process_element(self, xml): + s = "" + + if xml.text: + s += xml.text + for n in xml.getchildren(): + if n.tag == "emphasis": + s += self.at.cb("a", self.__process_element(n)) + if n.tag == "computeroutput": + s += self.at.cb("c", self.__process_element(n)) + if n.tag == "itemizedlist": + s += "\n" + self.__process_element(n) + if n.tag == "listitem": + s += " - " + self.__process_element(n) + if n.tag == "para": + s += self.__process_element(n) + "\n" + if n.tag == "ref": + s += n.text if n.text else "" + if n.tag == "simplesect": + ss = self.at.cb(n.get("kind"), self.__process_element(n)) + s += ss if ss + "\n" else "" + if n.tag == "programlisting": + s += self.get_program_listing(n) + if n.tag == "xrefsect": + s += self.__process_element(n) + if n.tag == "xreftitle": + s += self.__process_element(n) + ": " + if n.tag == "xrefdescription": + s += self.__process_element(n) + if n.tag == "ulink": + s += self.__process_element(n) + if n.tag == "linebreak": + s += "\n" + if n.tag == "ndash": + s += "--" + # workaround for doxygen bug #646002 + if n.tag == "htmlonly": + s += "" + if n.tail: + s += n.tail + if n.tag.startswith("param"): + pass # parameters are handled separately in DoxyFunction::from_memberdef() + return s + +class DoxyMember(object): + def __init__(self, name, brief, extra = ""): + self.name = name + self.brief = brief + self.extra = extra + + +class DoxyElement(object): + + def __init__(self, name, definition, **kwargs): + self.name = name + self.definition = definition + self.brief = kwargs.get('brief', "") + self.detail = kwargs.get('detail', "") + self.members = kwargs.get('members', []) + self.since = kwargs.get('since', "") + self.extra = kwargs.get('extra', "") + self.retval = kwargs.get('retval', None) + + def is_documented(self): + if (normalize_text(self.brief)) != "": + return True + return False + + def add_brief(self, xml): + proc = DoxygenProcess() + self.brief = proc.process_element(xml) + self.extra += proc.get_extra() + + def add_detail(self, xml): + proc = DoxygenProcess() + self.detail = proc.process_element(xml) + self.extra += proc.get_extra() + self.since = proc.get_since() + + def add_member(self, xml): + name = xml.find("name").text + proc = DoxygenProcess() + brief = proc.process_element(xml.find("briefdescription")) + # optional doxygen command output appears within <detaileddescription /> + proc.process_element(xml.find("detaileddescription")) + self.members.append(DoxyMember(name, normalize_text(brief), proc.get_extra())) + + def add_param(self, xml): + name = xml.find("parameternamelist").find("parametername").text + proc = DoxygenProcess() + brief = proc.process_element(xml.find("parameterdescription")) + self.members.append(DoxyMember(name, normalize_text(brief), proc.get_extra())) + + def add_return(self, xml): + proc = DoxygenProcess() + brief = proc.process_element(xml) + self.retval = DoxyMember("ret", normalize_text(brief), proc.get_extra()) + + def to_gtkdoc(self): + s = [] + s.append("/**") + s.append(" * %s: %s" % (self.name, self.extra)) + for p in self.members: + s.append(" * @%s: %s %s" % (p.name, p.extra, p.brief)) + s.append(" *") + s.append(" * %s" % self.brief.replace("\n", "\n * ")) + s.append(" *") + s.append(" * %s" % self.detail.replace("\n", "\n * ")) + s.append(" *") + if self.retval: + s.append(" * Returns: %s %s" % (self.retval.extra, self.retval.brief)) + if self.since: + s.append(" *") + s.append(" * Since: %s" % self.since) + s.append(" */") + s.append("") + return "\n".join(s) + +class DoxyTypedef(DoxyElement): + + @staticmethod + def from_memberdef(xml): + name = xml.find("name").text + d = normalize_text(xml.find("definition").text).replace("G_BEGIN_DECLS", "") + d += ";" + return DoxyTypedef(name, d) + +class DoxyEnum(DoxyElement): + + @staticmethod + def from_memberdef(xml): + name = xml.find("name").text + d = "typedef enum {\n" + for member in xml.findall("enumvalue"): + v = member.find("initializer") + d += "\t%s%s,\n" % ( member.find("name").text, " "+v.text if v is not None else "") + d += "} %s;\n" % name + + e = DoxyEnum(name, d) + e.add_brief(xml.find("briefdescription")) + for p in xml.findall("enumvalue"): + e.add_member(p) + return e + +class DoxyStruct(DoxyElement): + + @staticmethod + def from_compounddef(xml, typedefs = []): + name = xml.find("compoundname").text + section = xml.find("sectiondef") + d = "struct %s {\n" % name; + for p in section.findall("memberdef"): + # workaround for struct members. g-ir-scanner can't properly map struct members + # (beginning with struct GeanyFoo) to the typedef and assigns a generic type for them + # thus we fix that up here and enforce usage of the typedef. These are written + # out first, before any struct definition, for this reason + # Exception: there are no typedefs for GeanyFooPrivate so skip those. Their exact + # type isn't needed anyway + s = fix_definition(p.find("definition").text).lstrip() + words = s.split() + if (words[0] == "struct"): + if not (words[1].endswith("Private") or words[1].endswith("Private*")): + s = " ".join(words[1:]) + d += "\t%s;\n" % s + + d += "};\n" + e = DoxyStruct(name, d) + e.add_brief(xml.find("briefdescription")) + for p in section.findall("memberdef"): + e.add_member(p) + return e + +class DoxyFunction(DoxyElement): + + @staticmethod + def from_memberdef(xml): + name = xml.find("name").text + d = normalize_text(xml.find("definition").text.replace("G_BEGIN_DECLS", "")) + d += " " + xml.find("argsstring").text + ";" + d = normalize_text(d.replace("GEANY_API_SYMBOL", "")) + + e = DoxyFunction(name, d) + e.add_brief(xml.find("briefdescription")) + e.add_detail(xml.find("detaileddescription")) + for p in xml.xpath(".//detaileddescription/*/parameterlist[@kind='param']/parameteritem"): + e.add_param(p) + x = xml.xpath(".//detaileddescription/*/simplesect[@kind='return']") + if (len(x) > 0): + e.add_return(x[0]) + return e + +other = [] +typedefs = [] + +c_files = root.xpath(".//compounddef[@kind='file']/compoundname[substring(.,string-length(.)-1)='.c']/..") +h_files = root.xpath(".//compounddef[@kind='file']/compoundname[substring(.,string-length(.)-1)='.h']/..") + +for f in h_files: + if not (f.find("compoundname").text.endswith("private.h")): + for n0 in f.xpath(".//*/memberdef[@kind='typedef' and @prot='public']"): + if not (n0.find("type").text.replace("G_BEGIN_DECLS", "").lstrip().startswith("enum")): + e = DoxyTypedef.from_memberdef(n0) + typedefs.append(e) + + for n0 in f.xpath(".//*/memberdef[@kind='enum' and @prot='public']"): + e = DoxyEnum.from_memberdef(n0) + other.append(e) + +for n0 in root.xpath(".//compounddef[@kind='struct' and @prot='public']"): + e = DoxyStruct.from_compounddef(n0) + other.append(e) + +for f in c_files: + for n0 in f.xpath(".//*/memberdef[@kind='function' and @prot='public']"): + e = DoxyFunction.from_memberdef(n0) + other.append(e) + +outfile.write("#include <glib.h>\n") +outfile.write("#include <gtk/gtk.h>\n") +outfile.write("typedef struct _ScintillaObject ScintillaObject;\n") +outfile.write("typedef struct TMSourceFile TMSourceFile;\n") +outfile.write("typedef struct TMWorkspace TMWorkspace;\n") + +# write typedefs first, they are possibly undocumented but still required (even +# if they are documented, they must be written out without gtkdoc) +for e in typedefs: + outfile.write(e.definition) + outfile.write("\n\n") + +for e in filter(lambda x: x.is_documented(), other): + outfile.write("\n\n") + outfile.write(e.to_gtkdoc()) + outfile.write(e.definition) + outfile.write("\n\n") + if (e.name.startswith("sci_")): + scioutfile.write(e.to_gtkdoc().replace("sci_", "scintilla_object_")) + scioutfile.write(e.definition.replace("sci_", "scintilla_object_")) + scioutfile.write("\n\n") +
-------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).