1 /* extract doxygen comments from C++ header files
3 * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
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.
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.
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/>.
30 #include <clang-c/Index.h>
31 #include <clang-c/Documentation.h>
34 Dox2jsConfig () : clang_argc (3), clang_argv (0), excl_argc (0), excl_argv (0)
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");
44 for (int i = 0; i < clang_argc; ++i) {
47 for (int i = 0; excl_argv[i]; ++i) {
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);
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;
71 typedef std::map <std::string, std::string> ResultMap;
79 ProcessQueue (std::vector<char*>* files, ResultMap* r, Dox2jsConfig* c, pthread_mutex_t* lck, bool report, bool check)
84 , total (files->size ())
86 , report_progress (report)
87 , check_compile (check)
90 std::vector<char*>* fq;
93 pthread_mutex_t* lock;
102 kind_to_txt (CXCursor cursor)
104 CXCursorKind kind = clang_getCursorKind (cursor);
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";
123 escape_json (const std::string &s)
125 std::ostringstream o;
126 for (auto c = s.cbegin (); c != s.cend (); 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;
134 if ('\x00' <= *c && *c <= '\x1f') {
135 o << "\\u" << std::hex << std::setw (4) << std::setfill ('0') << (int)*c;
145 recurse_parents (CXCursor cr) {
147 CXCursor pc = clang_getCursorSemanticParent (cr);
148 if (CXCursor_TranslationUnit == clang_getCursorKind (pc)) {
151 if (!clang_Cursor_isNull (pc)) {
152 rv += recurse_parents (pc);
153 rv += clang_getCString (clang_getCursorDisplayName (pc));
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) {
171 static enum CXChildVisitResult
172 traverse (CXCursor cr, CXCursor /*parent*/, CXClientData d)
174 struct dox2js* dj = (struct dox2js*) d;
175 CXComment c = clang_Cursor_getParsedComment (cr);
177 if (clang_Comment_getKind (c) != CXComment_Null
178 && clang_isDeclaration (clang_getCursorKind (cr))
179 && 0 != strlen (kind_to_txt (cr))
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));
187 if (decl.empty () || check_excludes (decl, dj->cfg)) {
188 return CXChildVisit_Recurse;
191 std::ostringstream o;
192 o << "{ \"decl\" : \"" << decl << "\",\n";
194 if (clang_Cursor_isVariadic (cr)) {
195 o << " \"variadic\" : true,\n";
198 CXSourceLocation loc = clang_getCursorLocation (cr);
199 CXFile file; unsigned line, col, off;
200 clang_getFileLocation (loc, &file, &line, &col, &off);
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"
207 dj->results[decl] = o.str ();
209 return CXChildVisit_Recurse;
213 process_file (const char* fn, struct Dox2jsConfig *djc, bool check)
219 fprintf (stderr, "--- %s ---\n", fn);
221 CXIndex index = clang_createIndex (0, check ? 1 : 0);
222 CXTranslationUnit tu = clang_createTranslationUnitFromSourceFile (index, fn, djc->clang_argc, djc->clang_argv, 0, 0);
225 fprintf (stderr, "Cannot create translation unit for src: %s\n", fn);
229 clang_visitChildren (clang_getTranslationUnitCursor (tu), traverse, (CXClientData) &dj);
231 clang_disposeTranslationUnit (tu);
232 clang_disposeIndex (index);
237 process_thread (void *d)
239 struct ProcessQueue* proc = (struct ProcessQueue*) d;
240 pthread_mutex_lock (proc->lock);
243 if (proc->fq->empty ()) {
246 char* fn = strdup (proc->fq->back ());
247 proc->fq->pop_back ();
248 pthread_mutex_unlock (proc->lock);
250 ResultMap rm = process_file (fn, proc->djc, proc->check_compile);
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;
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,
265 pthread_mutex_unlock (proc->lock);
274 printf ("doxy2json - extract doxygen doc from C++ headers.\n\n");
275 fprintf (stderr, "Usage: dox2json [-I path]* [-X exclude]* <filename> [filename]*\n");
279 int main (int argc, char** argv)
281 struct Dox2jsConfig djc;
283 #define MAX_THREADS 16
284 pthread_t threads[MAX_THREADS];
286 bool report_progress = false;
287 bool check_compile = false;
288 size_t num_threads = 1;
290 while (EOF != (c = getopt (argc, argv, "j:D:I:TX:"))) {
293 num_threads = std::max (1, std::min ((int)MAX_THREADS, atoi (optarg)));
296 djc.add_clang_arg ("-I");
297 djc.add_clang_arg (optarg);
300 djc.add_clang_arg ("-D");
301 djc.add_clang_arg (optarg);
304 djc.add_exclude (optarg);
307 check_compile = true;
312 usage (EXIT_FAILURE);
317 if (optind >= argc) {
318 usage (EXIT_FAILURE);
321 const int total = (argc - optind);
322 if (total > 6 && !check_compile) {
323 report_progress = true;
327 std::vector<char*> src;
328 pthread_mutex_t lock;
329 pthread_mutex_init (&lock, NULL);
331 for (int i = optind; i < argc; ++i) {
332 src.push_back (argv[i]);
338 num_threads = std::min (src.size (), num_threads);
341 struct ProcessQueue proc (&src, &results, &djc, &lock, report_progress, check_compile);
343 for (unsigned int i = 0; i < num_threads; ++i) {
344 int rc = pthread_create (&threads[i], NULL, process_thread, (void *)&proc);
346 fprintf (stderr, "failed to create thread.\n");
353 for (unsigned int i = 0; i < num_threads; ++i) {
354 pthread_join (threads[i], NULL);
357 if (!check_compile) {
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 ());
365 pthread_mutex_destroy (&lock);