Only show user-presets in favorite sidebar
[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 <pthread.h>
23 #include <cstdio>
24 #include <cstring>
25 #include <sstream>
26 #include <iomanip>
27 #include <algorithm>
28 #include <map>
29 #include <vector>
30 #include <clang-c/Index.h>
31 #include <clang-c/Documentation.h>
32
33 struct Dox2jsConfig {
34         Dox2jsConfig () : clang_argc (3), clang_argv (0), excl_argc (0), excl_argv (0)
35         {
36                 excl_argv = (char**) calloc (1, sizeof (char*));
37                 clang_argv = (char**) malloc (clang_argc * sizeof (char*));
38                 clang_argv[0] = strdup ("-x");
39                 clang_argv[1] = strdup ("c++");
40                 clang_argv[2] = strdup ("-std=c++11");
41         }
42
43         ~Dox2jsConfig () {
44                 for (int i = 0; i < clang_argc; ++i) {
45                         free (clang_argv[i]);
46                 }
47                 for (int i = 0; excl_argv[i]; ++i) {
48                         free (excl_argv[i]);
49                 }
50                 free (clang_argv);
51                 free (excl_argv);
52         }
53
54         void add_clang_arg (const char *a) {
55                 clang_argv = (char**) realloc (clang_argv, (clang_argc + 1) * sizeof (char*));
56                 clang_argv[clang_argc++] = strdup (a);
57         }
58
59         void add_exclude (const char *a) {
60                 excl_argv = (char**) realloc (excl_argv, (excl_argc + 2) * sizeof (char*));
61                 excl_argv[excl_argc++] = strdup (a);
62                 excl_argv[excl_argc] = NULL;
63         }
64
65         int    clang_argc;
66         char** clang_argv;
67         int    excl_argc;
68         char** excl_argv;
69 };
70
71 typedef std::map <std::string, std::string> ResultMap;
72
73 struct dox2js {
74         Dox2jsConfig *cfg;
75         ResultMap results;
76 };
77
78 struct ProcessQueue {
79         ProcessQueue (std::vector<char*>* files, ResultMap* r, Dox2jsConfig* c, pthread_mutex_t* lck, bool report, bool check)
80                 : fq (files)
81                 , rm (r)
82                 , djc (c)
83                 , lock (lck)
84                 , total (files->size ())
85                 , done (0)
86                 , report_progress (report)
87                 , check_compile (check)
88         { }
89
90         std::vector<char*>* fq;
91         ResultMap *rm;
92         Dox2jsConfig *djc;
93         pthread_mutex_t* lock;
94         unsigned int total;
95         unsigned int done;
96         bool report_progress;
97         bool check_compile;
98 };
99
100
101 static const char*
102 kind_to_txt (CXCursor cursor)
103 {
104         CXCursorKind kind  = clang_getCursorKind (cursor);
105         switch (kind) {
106                 case CXCursor_StructDecl   : return "Struct";
107                 case CXCursor_EnumDecl     : return "Enum";
108                 case CXCursor_UnionDecl    : return "Union";
109                 case CXCursor_FunctionDecl : return "C Function";
110                 case CXCursor_VarDecl      : return "Variable";
111                 case CXCursor_ClassDecl    : return "C++ Class";
112                 case CXCursor_CXXMethod    : return "C++ Method";
113                 case CXCursor_Namespace    : return "C++ Namespace";
114                 case CXCursor_Constructor  : return "C++ Constructor";
115                 case CXCursor_Destructor   : return "C++ Destructor";
116                 case CXCursor_FieldDecl    : return "Data Member/Field";
117                 default: break;
118         }
119         return "";
120 }
121
122 static std::string
123 escape_json (const std::string &s)
124 {
125         std::ostringstream o;
126         for (auto c = s.cbegin (); c != s.cend (); c++) {
127                 switch (*c) {
128                         case '"':  o << "\\\""; break;
129                         case '\\': o << "\\\\"; break;
130                         case '\n': o << "\\n"; break;
131                         case '\r': o << "\\r"; break;
132                         case '\t': o << "\\t"; break;
133                         default:
134                                 if ('\x00' <= *c && *c <= '\x1f') {
135                                         o << "\\u" << std::hex << std::setw (4) << std::setfill ('0') << (int)*c;
136                                 } else {
137                                   o << *c;
138                                 }
139                 }
140         }
141         return o.str ();
142 }
143
144 static std::string
145 recurse_parents (CXCursor cr) {
146         std::string rv;
147         CXCursor pc = clang_getCursorSemanticParent (cr);
148         if (CXCursor_TranslationUnit == clang_getCursorKind (pc)) {
149                 return rv;
150         }
151         if (!clang_Cursor_isNull (pc)) {
152                 rv += recurse_parents (pc);
153                 rv += clang_getCString (clang_getCursorDisplayName (pc));
154                 rv += "::";
155         }
156         return rv;
157 }
158
159 static bool
160 check_excludes (const std::string& decl, CXClientData d) {
161         struct Dox2jsConfig* djc = (struct Dox2jsConfig*) d;
162         char** excl = djc->excl_argv;
163         for (int i = 0; excl[i]; ++i) {
164                 if (decl.compare (0, strlen (excl[i]), excl[i]) == 0) {
165                         return true;
166                 }
167         }
168         return false;
169 }
170
171 static enum CXChildVisitResult
172 traverse (CXCursor cr, CXCursor /*parent*/, CXClientData d)
173 {
174         struct dox2js* dj = (struct dox2js*) d;
175         CXComment c = clang_Cursor_getParsedComment (cr);
176
177         if (clang_Comment_getKind (c) != CXComment_Null
178                         && clang_isDeclaration (clang_getCursorKind (cr))
179                         && 0 != strlen (kind_to_txt (cr))
180                  ) {
181
182                 // TODO: resolve typedef enum { .. } name;
183                 // use clang_getCursorDefinition (clang_getCanonicalCursor (cr)) ??
184                 std::string decl = recurse_parents (cr);
185                 decl += clang_getCString (clang_getCursorDisplayName (cr));
186
187                 if (decl.empty () || check_excludes (decl, dj->cfg)) {
188                         return CXChildVisit_Recurse;
189                 }
190
191                 std::ostringstream o;
192                 o << "{ \"decl\" : \"" << decl << "\",\n";
193
194                 if (clang_Cursor_isVariadic (cr)) {
195                         o << "  \"variadic\" : true,\n";
196                 }
197
198                 CXSourceLocation  loc = clang_getCursorLocation (cr);
199                 CXFile file; unsigned line, col, off;
200                 clang_getFileLocation (loc, &file, &line, &col, &off);
201
202                 o << "  \"kind\" : \"" << kind_to_txt (cr) << "\",\n"
203                         << "  \"src\" : \"" << clang_getCString (clang_getFileName (file)) << ":" << line << "\",\n"
204                         << "  \"doc\" : \"" << escape_json (clang_getCString (clang_FullComment_getAsHTML (c))) << "\"\n"
205                         << "},\n";
206
207                 dj->results[decl] = o.str ();
208         }
209         return CXChildVisit_Recurse;
210 }
211
212 static ResultMap
213 process_file (const char* fn, struct Dox2jsConfig *djc, bool check)
214 {
215         dox2js dj;
216         dj.cfg = djc;
217
218         if (check) {
219                 fprintf (stderr, "--- %s ---\n", fn);
220         }
221         CXIndex index = clang_createIndex (0, check ? 1 : 0);
222         CXTranslationUnit tu = clang_createTranslationUnitFromSourceFile (index, fn, djc->clang_argc, djc->clang_argv, 0, 0);
223
224         if (tu == NULL) {
225                 fprintf (stderr, "Cannot create translation unit for src: %s\n", fn);
226                 return ResultMap ();
227         }
228
229         clang_visitChildren (clang_getTranslationUnitCursor (tu), traverse, (CXClientData) &dj);
230
231         clang_disposeTranslationUnit (tu);
232         clang_disposeIndex (index);
233         return dj.results;
234 }
235
236 static void*
237 process_thread (void *d)
238 {
239         struct ProcessQueue* proc = (struct ProcessQueue*) d;
240         pthread_mutex_lock (proc->lock);
241
242         while (1) {
243                 if (proc->fq->empty ()) {
244                         break;
245                 }
246                 char* fn = strdup (proc->fq->back ());
247                 proc->fq->pop_back ();
248                 pthread_mutex_unlock (proc->lock);
249
250                 ResultMap rm = process_file (fn, proc->djc, proc->check_compile);
251                 free (fn);
252
253                 pthread_mutex_lock (proc->lock);
254                 for (ResultMap::const_iterator i = rm.begin (); i != rm.end (); ++i) {
255                         (*proc->rm)[i->first] = i->second;
256                 }
257                 ++proc->done;
258
259                 if (proc->report_progress) {
260                         fprintf (stderr, "progress: %4.1f%%  [%4d / %4d] decl: %ld         \r",
261                                         100.f * proc->done / (float)proc->total, proc->done, proc->total,
262                                         proc->rm->size ());
263                 }
264         }
265         pthread_mutex_unlock (proc->lock);
266         pthread_exit (NULL);
267         return NULL;
268 }
269
270
271 static void
272 usage (int status)
273 {
274         printf ("doxy2json - extract doxygen doc from C++ headers.\n\n");
275         fprintf (stderr, "Usage: dox2json [-I path]* [-X exclude]* <filename> [filename]*\n");
276         exit (status);
277 }
278
279 int main (int argc, char** argv)
280 {
281         struct Dox2jsConfig djc;
282
283 #define MAX_THREADS 16
284         pthread_t threads[MAX_THREADS];
285
286         bool report_progress = false;
287         bool check_compile = false;
288         size_t num_threads = 1;
289   int c;
290         while (EOF != (c = getopt (argc, argv, "j:D:I:TX:"))) {
291                 switch (c) {
292                         case 'j':
293                                 num_threads = std::max (1, std::min ((int)MAX_THREADS, atoi (optarg)));
294                                 break;
295                         case 'I':
296                                 djc.add_clang_arg ("-I");
297                                 djc.add_clang_arg (optarg);
298                                 break;
299                         case 'D':
300                                 djc.add_clang_arg ("-D");
301                                 djc.add_clang_arg (optarg);
302                                 break;
303                         case 'X':
304                                 djc.add_exclude (optarg);
305                                 break;
306                         case 'T':
307                                 check_compile = true;
308                                 break;
309                         case 'h':
310                                 usage (0);
311                         default:
312                                 usage (EXIT_FAILURE);
313                                 break;
314                 }
315         }
316
317         if (optind >= argc) {
318                 usage (EXIT_FAILURE);
319         }
320
321         const int total = (argc - optind);
322         if (total > 6 && !check_compile) {
323                 report_progress = true;
324         }
325
326         ResultMap results;
327         std::vector<char*> src;
328         pthread_mutex_t lock;
329         pthread_mutex_init (&lock, NULL);
330
331         for (int i = optind; i < argc; ++i) {
332                 src.push_back (argv[i]);
333         }
334
335         if (check_compile) {
336                 num_threads = 1;
337         } else {
338                 num_threads = std::min (src.size (), num_threads);
339         }
340
341         struct ProcessQueue proc (&src, &results, &djc, &lock, report_progress, check_compile);
342
343         for (unsigned int i = 0; i < num_threads; ++i) {
344                 int rc = pthread_create (&threads[i], NULL, process_thread, (void *)&proc);
345                 if (rc) {
346                         fprintf (stderr, "failed to create thread.\n");
347                         exit(EXIT_FAILURE);
348                 }
349         }
350
351         pthread_yield();
352
353         for (unsigned int i = 0; i < num_threads; ++i) {
354                 pthread_join (threads[i], NULL);
355         }
356
357         if (!check_compile) {
358                 printf ("[\n");
359                 for (std::map <std::string, std::string>::const_iterator i = results.begin (); i != results.end (); ++i) {
360                         printf ("%s\n", (*i).second.c_str ());
361                 }
362                 printf ("{} ]\n");
363         }
364
365         pthread_mutex_destroy (&lock);
366
367   return 0;
368 }