update documentation extraction script (use .cc instead of .h)
[ardour.git] / tools / doxy2json / doxy2json.cc
1 /* extract doxygen comments from C++ header files
2  *
3  * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <getopt.h>
22 #include <cstdio>
23 #include <cstring>
24 #include <sstream>
25 #include <iomanip>
26 #include <map>
27 #include <clang-c/Index.h>
28 #include <clang-c/Documentation.h>
29
30 struct dox2js {
31         dox2js () : clang_argc (3), clang_argv (0), excl_argc (0), excl_argv (0)
32         {
33                 excl_argv = (char**) calloc (1, sizeof (char*));
34                 clang_argv = (char**) malloc (clang_argc * sizeof (char*));
35                 clang_argv[0] = strdup ("-x");
36                 clang_argv[1] = strdup ("c++");
37                 clang_argv[2] = strdup ("-std=c++11");
38         }
39
40         ~dox2js () {
41                 for (int i = 0; i < clang_argc; ++i) {
42                         free (clang_argv[i]);
43                 }
44                 for (int i = 0; excl_argv[i]; ++i) {
45                         free (excl_argv[i]);
46                 }
47                 free (clang_argv);
48                 free (excl_argv);
49         }
50
51         void add_clang_arg (const char *a) {
52                 clang_argv = (char**) realloc (clang_argv, (clang_argc + 1) * sizeof (char*));
53                 clang_argv[clang_argc++] = strdup (a);
54         }
55
56         void add_exclude (const char *a) {
57                 excl_argv = (char**) realloc (excl_argv, (excl_argc + 2) * sizeof (char*));
58                 excl_argv[excl_argc++] = strdup (a);
59                 excl_argv[excl_argc] = NULL;
60         }
61
62         int    clang_argc;
63         char** clang_argv;
64         int    excl_argc;
65         char** excl_argv;
66         std::map <std::string, std::string> results;
67 };
68
69 static const char*
70 kind_to_txt (CXCursor cursor)
71 {
72         CXCursorKind kind  = clang_getCursorKind (cursor);
73         switch (kind) {
74                 case CXCursor_StructDecl   : return "Struct";
75                 case CXCursor_EnumDecl     : return "Enum";
76                 case CXCursor_UnionDecl    : return "Union";
77                 case CXCursor_FunctionDecl : return "C Function";
78                 case CXCursor_VarDecl      : return "Variable";
79                 case CXCursor_ClassDecl    : return "C++ Class";
80                 case CXCursor_CXXMethod    : return "C++ Method";
81                 case CXCursor_Namespace    : return "C++ Namespace";
82                 case CXCursor_Constructor  : return "C++ Constructor";
83                 case CXCursor_Destructor   : return "C++ Destructor";
84                 case CXCursor_FieldDecl    : return "Data Member/Field";
85                 default: break;
86         }
87         return "";
88 }
89
90 static std::string
91 escape_json (const std::string &s)
92 {
93         std::ostringstream o;
94         for (auto c = s.cbegin (); c != s.cend (); c++) {
95                 switch (*c) {
96                         case '"':  o << "\\\""; break;
97                         case '\\': o << "\\\\"; break;
98                         case '\n': o << "\\n"; break;
99                         case '\r': o << "\\r"; break;
100                         case '\t': o << "\\t"; break;
101                         default:
102                                 if ('\x00' <= *c && *c <= '\x1f') {
103                                         o << "\\u" << std::hex << std::setw (4) << std::setfill ('0') << (int)*c;
104                                 } else {
105                                   o << *c;
106                                 }
107                 }
108         }
109         return o.str ();
110 }
111
112 static std::string
113 recurse_parents (CXCursor cr) {
114         std::string rv;
115         CXCursor pc = clang_getCursorSemanticParent (cr);
116         if (CXCursor_TranslationUnit == clang_getCursorKind (pc)) {
117                 return rv;
118         }
119         if (!clang_Cursor_isNull (pc)) {
120                 rv += recurse_parents (pc);
121                 rv += clang_getCString (clang_getCursorDisplayName (pc));
122                 rv += "::";
123         }
124         return rv;
125 }
126
127 static bool
128 check_excludes (const std::string& decl, CXClientData d) {
129         struct dox2js* dj = (struct dox2js*) d;
130         char** excl = dj->excl_argv;
131         for (int i = 0; excl[i]; ++i) {
132                 if (decl.compare (0, strlen (excl[i]), excl[i]) == 0) {
133                         return true;
134                 }
135         }
136         return false;
137 }
138
139 static enum CXChildVisitResult
140 traverse (CXCursor cr, CXCursor /*parent*/, CXClientData d)
141 {
142         struct dox2js* dj = (struct dox2js*) d;
143         CXComment c = clang_Cursor_getParsedComment (cr);
144
145         if (clang_Comment_getKind (c) != CXComment_Null
146                         && clang_isDeclaration (clang_getCursorKind (cr))
147                         && 0 != strlen (kind_to_txt (cr))
148                  ) {
149
150                 // TODO: resolve typedef enum { .. } name;
151                 // use clang_getCursorDefinition (clang_getCanonicalCursor (cr)) ??
152                 std::string decl = recurse_parents (cr);
153                 decl += clang_getCString (clang_getCursorDisplayName (cr));
154
155                 if (decl.empty () || check_excludes (decl, d)) {
156                         return CXChildVisit_Recurse;
157                 }
158
159                 std::ostringstream o;
160                 o << "{ \"decl\" : \"" << decl << "\",\n";
161
162                 if (clang_Cursor_isVariadic (cr)) {
163                         o << "  \"variadic\" : true,\n";
164                 }
165
166                 CXSourceLocation  loc = clang_getCursorLocation (cr);
167                 CXFile file; unsigned line, col, off;
168                 clang_getFileLocation (loc, &file, &line, &col, &off);
169
170                 o << "  \"kind\" : \"" << kind_to_txt (cr) << "\",\n"
171                         << "  \"src\" : \"" << clang_getCString (clang_getFileName (file)) << ":" << line << "\",\n"
172                         << "  \"doc\" : \"" << escape_json (clang_getCString (clang_FullComment_getAsHTML (c))) << "\"\n"
173                         << "},\n";
174
175                 dj->results[decl] = o.str ();
176         }
177         return CXChildVisit_Recurse;
178 }
179
180 static void
181 process_file (const char* fn, struct dox2js *dj, bool check)
182 {
183         if (check) {
184                 fprintf (stderr, "--- %s ---\n", fn);
185         }
186         CXIndex index = clang_createIndex (0, check ? 1 : 0);
187         CXTranslationUnit tu = clang_createTranslationUnitFromSourceFile (index, fn, dj->clang_argc, dj->clang_argv, 0, 0);
188
189         if (tu == NULL) {
190                 fprintf (stderr, "Cannot create translation unit for src: %s\n", fn);
191                 return;
192         }
193
194         clang_visitChildren (clang_getTranslationUnitCursor (tu), traverse, (CXClientData) dj);
195
196         clang_disposeTranslationUnit (tu);
197         clang_disposeIndex (index);
198 }
199
200 static void
201 usage (int status)
202 {
203         printf ("doxy2json - extract doxygen doc from C++ headers.\n\n");
204         fprintf (stderr, "Usage: dox2json [-I path]* [-X exclude]* <filename> [filename]*\n");
205         exit (status);
206 }
207
208 int main (int argc, char** argv)
209 {
210         struct dox2js dj;
211
212         bool report_progress = false;
213         bool check_compile = false;
214   int c;
215         while (EOF != (c = getopt (argc, argv, "D:I:TX:"))) {
216                 switch (c) {
217                         case 'I':
218                                 dj.add_clang_arg ("-I");
219                                 dj.add_clang_arg (optarg);
220                                 break;
221                         case 'D':
222                                 dj.add_clang_arg ("-D");
223                                 dj.add_clang_arg (optarg);
224                                 break;
225                         case 'X':
226                                 dj.add_exclude (optarg);
227                                 break;
228                         case 'T':
229                                 check_compile = true;
230                                 break;
231                         case 'h':
232                                 usage (0);
233                         default:
234                                 usage (EXIT_FAILURE);
235                                 break;
236                 }
237         }
238
239         if (optind >= argc) {
240                 usage (EXIT_FAILURE);
241         }
242
243         const int total = (argc - optind);
244         if (total > 6 && !check_compile) {
245                 report_progress = true;
246         }
247
248         for (int i = optind; i < argc; ++i) {
249                 process_file (argv[i], &dj, check_compile);
250                 if (report_progress) {
251                         fprintf (stderr, "progress: %4.1f%%  [%4d / %4d] decl: %ld         \r",
252                                         100.f * (1.f + i - optind) / (float)total, i - optind, total,
253                                         dj.results.size ());
254                         fflush (stderr);
255                 }
256         }
257
258         if (!check_compile) {
259                 printf ("[\n");
260                 for (std::map <std::string, std::string>::const_iterator i = dj.results.begin (); i != dj.results.end (); ++i) {
261                         printf ("%s\n", (*i).second.c_str ());
262                 }
263                 printf ("{} ]\n");
264         }
265
266   return 0;
267 }