xref: /Universal-ctags/main/writer-json.c (revision 0b491fdf2257a9d084b3b4e41485d7146b5c4381)
1 /*
2 *   Copyright (c) 2016, Aman Gupta
3 *
4 *   This source code is released for free distribution under the terms of the
5 *   GNU General Public License version 2 or (at your option) any later version.
6 *
7 *   External interface to entry.c
8 */
9 
10 #include "general.h"  /* must always come first */
11 
12 #include "debug.h"
13 #include "entry_p.h"
14 #include "field_p.h"
15 #include "mio.h"
16 #include "options_p.h"
17 #include "read.h"
18 #include "routines.h"
19 #include "ptag_p.h"
20 #include "writer_p.h"
21 
22 
23 #include <string.h>
24 
25 #ifdef HAVE_JANSSON
26 #include <jansson.h>
27 
28 #ifndef json_boolean /* compat with jansson < 2.4 */
29 #define json_boolean(val)      ((val) ? json_true() : json_false())
30 #endif
31 
32 
33 static int writeJsonEntry  (tagWriter *writer CTAGS_ATTR_UNUSED,
34 				MIO * mio, const tagEntryInfo *const tag,
35 				void *clientData);
36 
37 static int writeJsonPtagEntry (tagWriter *writer CTAGS_ATTR_UNUSED,
38 				MIO * mio, const ptagDesc *desc,
39 				const char *const fileName,
40 				const char *const pattern,
41 				const char *const parserName,
42 				void *clientData);
43 
44 tagWriter jsonWriter = {
45 	.writeEntry = writeJsonEntry,
46 	.writePtagEntry = writeJsonPtagEntry,
47 	.printPtagByDefault = true,
48 	.preWriteEntry = NULL,
49 	.postWriteEntry = NULL,
50 	.rescanFailedEntry = NULL,
51 	.treatFieldAsFixed = NULL,
52 	.defaultFileName = NULL,
53 };
54 
escapeFieldValueRaw(const tagEntryInfo * tag,fieldType ftype,int fieldIndex)55 static const char* escapeFieldValueRaw (const tagEntryInfo * tag, fieldType ftype, int fieldIndex)
56 {
57 	const char *v;
58 	if (doesFieldHaveRenderer(ftype, true))
59 		v = renderFieldNoEscaping (ftype, tag, fieldIndex);
60 	else
61 		v = renderField (ftype, tag, fieldIndex);
62 
63 	return v;
64 }
65 
escapeFieldValue(const tagEntryInfo * tag,fieldType ftype,bool returnEmptyStringAsNoValue)66 static json_t* escapeFieldValue (const tagEntryInfo * tag, fieldType ftype, bool returnEmptyStringAsNoValue)
67 {
68 	const char *str = escapeFieldValueRaw (tag, ftype, NO_PARSER_FIELD);
69 
70 	if (str)
71 	{
72 		unsigned int dt = getFieldDataType(ftype);
73 		if (dt & FIELDTYPE_STRING)
74 		{
75 			if (dt & FIELDTYPE_BOOL && str[0] == '\0')
76 				return json_false();
77 			else
78 				return json_string (str);
79 		}
80 		else if (dt & FIELDTYPE_INTEGER)
81 		{
82 			long tmp;
83 
84 			if (strToLong (str, 10, &tmp))
85 				return json_integer (tmp);
86 			else
87 				return NULL;
88 		}
89 		else if (dt & FIELDTYPE_BOOL)
90 		{
91 			/* TODO: This must be fixed when new boolean field is added.
92 			   Currently only `file:' field use this. */
93 			return json_boolean (strcmp ("-", str)); /* "-" -> false */
94 		}
95 		AssertNotReached ();
96 		return NULL;
97 	}
98 	else if (returnEmptyStringAsNoValue)
99 		return json_false();
100 	else
101 		return NULL;
102 }
103 
renderExtensionFieldMaybe(int xftype,const tagEntryInfo * const tag,json_t * response)104 static void renderExtensionFieldMaybe (int xftype, const tagEntryInfo *const tag, json_t *response)
105 {
106 	const char *fname = getFieldName (xftype);
107 
108 	if (fname && doesFieldHaveRenderer (xftype, false) && isFieldEnabled (xftype) && doesFieldHaveValue (xftype, tag))
109 	{
110 		switch (xftype)
111 		{
112 		case FIELD_LINE_NUMBER:
113 			json_object_set_new (response, fname,
114 					     json_integer (tag->lineNumber));
115 			break;
116 		case FIELD_FILE_SCOPE:
117 			json_object_set_new (response, fname,
118 					     json_boolean(1));
119 			break;
120 		default:
121 			json_object_set_new (response, fname,
122 					     escapeFieldValue (tag, xftype, false));
123 		}
124 	}
125 }
126 
addParserFields(json_t * response,const tagEntryInfo * const tag)127 static void addParserFields (json_t *response, const tagEntryInfo *const tag)
128 {
129 	unsigned int i;
130 
131 	for (i = 0; i < tag->usedParserFields; i++)
132 	{
133 		const tagField *f = getParserFieldForIndex(tag, i);
134 		fieldType ftype = f->ftype;
135 		if (! isFieldEnabled (ftype))
136 			continue;
137 
138 		unsigned int dt = getFieldDataType (ftype);
139 		json_t *o;
140 		if (dt & FIELDTYPE_STRING)
141 		{
142 			const char *str = escapeFieldValueRaw (tag, ftype, i);
143 			if (dt & FIELDTYPE_BOOL && str[0] == '\0')
144 				o = json_false ();
145 			else
146 				o = json_string (str);
147 		}
148 		else if (dt & FIELDTYPE_INTEGER)
149 		{
150 			/* NOT IMPLEMENTED YET */
151 			AssertNotReached ();
152 			o = json_null ();
153 		}
154 		else if (dt & FIELDTYPE_BOOL)
155 			o = json_true ();
156 		else
157 		{
158 			AssertNotReached ();
159 			o = json_null ();
160 		}
161 
162 		json_object_set_new (response, getFieldName (ftype), o);
163 	}
164 }
165 
addExtensionFields(json_t * response,const tagEntryInfo * const tag)166 static void addExtensionFields (json_t *response, const tagEntryInfo *const tag)
167 {
168 	int k;
169 
170 	/* FIELD_KIND has no name; getFieldName (FIELD_KIND) returns NULL.
171 	   FIELD_KIND_LONG does, too.
172 	   That cannot be changed to keep the compatibility of tags file format.
173 	   Use FIELD_KIND_KEY instead */
174 	if (isFieldEnabled (FIELD_KIND) || isFieldEnabled (FIELD_KIND_LONG))
175 		enableField (FIELD_KIND_KEY, true);
176 
177 	/* FIELD_SCOPE has no name; getFieldName (FIELD_KIND_KEY) returns NULL.
178 	   That cannot be changed to keep the compatibility of tags file format.
179 	   Use FIELD_SCOPE_KEY and FIELD_SCOPE_KIND_LONG instead. */
180 	if (isFieldEnabled (FIELD_SCOPE))
181 	{
182 		enableField (FIELD_SCOPE_KEY, true);
183 		enableField (FIELD_SCOPE_KIND_LONG, true);
184 	}
185 
186 	for (k = FIELD_JSON_LOOP_START; k <= FIELD_BUILTIN_LAST; k++)
187 		renderExtensionFieldMaybe (k, tag, response);
188 }
189 
writeJsonEntry(tagWriter * writer CTAGS_ATTR_UNUSED,MIO * mio,const tagEntryInfo * const tag,void * clientData CTAGS_ATTR_UNUSED)190 static int writeJsonEntry (tagWriter *writer CTAGS_ATTR_UNUSED,
191 			       MIO * mio, const tagEntryInfo *const tag,
192 				   void *clientData CTAGS_ATTR_UNUSED)
193 {
194 	int length = 0;
195 	json_t *response = json_pack ("{ss}", "_type", "tag");
196 
197 	if (isFieldEnabled (FIELD_NAME))
198 	{
199 		json_t *name = json_string (tag->name);
200 		if (name == NULL)
201 			goto out;
202 		json_object_set_new (response, "name", name);
203 	}
204 	if (isFieldEnabled (FIELD_INPUT_FILE))
205 		json_object_set_new (response, "path", json_string (tag->sourceFileName));
206 	if (isFieldEnabled (FIELD_PATTERN))
207 	{
208 		json_t *pat = escapeFieldValue(tag, FIELD_PATTERN, true);
209 		json_object_set_new (response, "pattern", pat);
210 	}
211 
212 	if (includeExtensionFlags ())
213 	{
214 		addExtensionFields (response, tag);
215 		addParserFields (response, tag);
216 	}
217 
218 	/* Print nothing if RESPONSE has only "_type" field. */
219 	if (json_object_size (response) == 1)
220 		goto out;
221 
222 	char *buf = json_dumps (response, JSON_PRESERVE_ORDER);
223 	length = mio_printf (mio, "%s\n", buf);
224 
225 	free (buf);
226  out:
227 	json_decref (response);
228 
229 	return length;
230 }
231 
writeJsonPtagEntry(tagWriter * writer CTAGS_ATTR_UNUSED,MIO * mio,const ptagDesc * desc,const char * const fileName,const char * const pattern,const char * const parserName,void * clientData CTAGS_ATTR_UNUSED)232 static int writeJsonPtagEntry (tagWriter *writer CTAGS_ATTR_UNUSED,
233 			       MIO * mio, const ptagDesc *desc,
234 			       const char *const fileName,
235 			       const char *const pattern,
236 			       const char *const parserName,
237 				   void *clientData CTAGS_ATTR_UNUSED)
238 {
239 #define OPT(X) ((X)?(X):"")
240 	json_t *response;
241 
242 	if (parserName)
243 	{
244 		response = json_pack ("{ss ss ss ss ss}",
245 				      "_type", "ptag",
246 				      "name", desc->name,
247 				      "parserName", parserName,
248 				      "path", OPT(fileName),
249 				      "pattern", OPT(pattern));
250 	}
251 	else
252 	{
253 		response = json_pack ("{ss ss ss ss}",
254 				      "_type", "ptag",
255 				      "name", desc->name,
256 				      "path", OPT(fileName),
257 				      "pattern", OPT(pattern));
258 	}
259 
260 	char *buf = json_dumps (response, JSON_PRESERVE_ORDER);
261 	int length = mio_printf (mio, "%s\n", buf);
262 	free (buf);
263 	json_decref (response);
264 
265 	return length;
266 #undef OPT
267 }
268 
ptagMakeJsonOutputVersion(ptagDesc * desc,langType language CTAGS_ATTR_UNUSED,const void * data CTAGS_ATTR_UNUSED)269 extern bool ptagMakeJsonOutputVersion (ptagDesc *desc, langType language CTAGS_ATTR_UNUSED,
270 									   const void *data CTAGS_ATTR_UNUSED)
271 {
272 	return writePseudoTag (desc,
273 			       "0.0",
274 			       "in development",
275 			       NULL);
276 }
277 
278 #else /* HAVE_JANSSON */
279 
280 tagWriter jsonWriter = {
281 	.writeEntry = NULL,
282 	.writePtagEntry = NULL,
283 	.preWriteEntry = NULL,
284 	.postWriteEntry = NULL,
285 	.defaultFileName = "-",
286 };
287 
ptagMakeJsonOutputVersion(ptagDesc * desc,langType language CTAGS_ATTR_UNUSED,const void * data CTAGS_ATTR_UNUSED)288 extern bool ptagMakeJsonOutputVersion (ptagDesc *desc, langType language CTAGS_ATTR_UNUSED,
289 									   const void *data CTAGS_ATTR_UNUSED)
290 {
291 	return false;
292 }
293 
294 #endif
295