xref: /Universal-ctags/parsers/gemspec.c (revision 197e8d50b7068db538f51c52f1cd77b6694e0874)
1 /*
2 * A parser for RUBYGEM's specification
3 *
4 * Copyright (c) 2021, Red Hat, Inc.
5 * Copyright (c) 2021, Masatake YAMATO
6 *
7 * Author: Masatake YAMATO <yamato@redhat.com>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22 * USA.
23 *
24 * Reference:
25 * - https://guides.rubygems.org/specification-reference/
26 *
27 * See also:
28 * - https://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
29 */
30 
31 /*
32 *   INCLUDE FILES
33 */
34 #include "general.h"  /* must always come first */
35 
36 #include "entry.h"
37 #include "parse.h"
38 #include "subparser.h"
39 
40 #include "ruby.h"
41 
42 #include <string.h>
43 
44 /*
45 * DATA STRUCTURES
46 */
47 struct sGemSpecSubparser {
48 	rubySubparser ruby;
49 	vString *var_name;
50 };
51 
52 typedef enum {
53 	K_GEM,
54 } gemspecKind;
55 
56 typedef enum {
57 	R_GEM_RUNTIME_DEP,
58 	R_GEM_DEVEL_DEP,
59 } gemspecGemRole;
60 
61 static roleDefinition GemSpecGemRoles [] = {
62 	{ true, "runtimeDep", "specifying runtime dependency" },
63 	{ true, "develDep",   "specifying development dependency" },
64 };
65 
66 static kindDefinition GemSpecKinds [] = {
67 	{ true, 'g', "gem", "gems",
68 	  .referenceOnly = false, ATTACH_ROLES(GemSpecGemRoles)},
69 };
70 
71 /*
72 * FUNCTIONS
73 */
findGemSpecTags(void)74 static void findGemSpecTags (void)
75 {
76 	scheduleRunningBaseparser (0);
77 }
78 
lineNotify(rubySubparser * s,const unsigned char ** cp)79 static int lineNotify (rubySubparser *s, const unsigned char **cp)
80 {
81 	struct sGemSpecSubparser *gemspec = (struct sGemSpecSubparser *)s;
82 	const unsigned char *p = *cp;
83 
84 	if (vStringLength (gemspec->var_name) > 0
85 		&& (strncmp ((char *)p, vStringValue (gemspec->var_name),
86 					 vStringLength (gemspec->var_name)) == 0))
87 	{
88 		int kind = K_GEM;
89 		int role = ROLE_DEFINITION_INDEX;
90 		bool is_attr = true;
91 
92 		p += vStringLength (gemspec->var_name);
93 
94 		if (strncmp ((const char *)p, "name", 4) == 0)
95 			p += 4;
96 		else if (strncmp ((const char *)p, "add_runtime_dependency", 22) == 0)
97 		{
98 			p += 22;
99 			role = R_GEM_RUNTIME_DEP;
100 			is_attr = false;
101 		}
102 		else if (strncmp ((const char *)p, "add_development_dependency", 26) == 0)
103 		{
104 			p += 26;
105 			role = R_GEM_DEVEL_DEP;
106 			is_attr = false;
107 		}
108 		else
109 			p = NULL;
110 
111 		if (p)
112 		{
113 			rubySkipWhitespace (&p);
114 			if ((!is_attr) || *p == '=')
115 			{
116 				if (is_attr)
117 				{
118 					p++;
119 					rubySkipWhitespace (&p);
120 				}
121 				if (*p == '"' || *p == '\'')
122 				{
123 					unsigned char b = *p;
124 					vString *gem = vStringNew ();
125 					p++;
126 					if (rubyParseString (&p, b, gem))
127 					{
128 						if (role == ROLE_DEFINITION_INDEX)
129 							makeSimpleTag (gem, kind);
130 						else
131 							makeSimpleRefTag (gem, kind, role);
132 					}
133 					vStringDelete (gem);
134 				}
135 			}
136 		}
137 	}
138 	else if (rubyCanMatchKeywordWithAssign (&p, "Gem::Specification.new"))
139 	{
140 		vString *vstr = vStringNew ();
141 		bool curly_bracket = false;
142 
143 		rubySkipWhitespace (&p);
144 
145 		curly_bracket = (*p == '{');
146 		if (curly_bracket
147 			|| (rubyParseMethodName (&p, vstr)
148 				&& vStringLength (vstr) == 2
149 				&& strcmp (vStringValue(vstr), "do") == 0))
150 		{
151 			if (curly_bracket)
152 				p++;
153 
154 			rubySkipWhitespace (&p);
155 			if (*p == '|')
156 			{
157 				p++;
158 				rubySkipWhitespace (&p);
159 				vStringClear (vstr);
160 				if (rubyParseMethodName (&p, vstr))
161 				{
162 					vStringPut (vstr, '.');
163 					vStringCopy(gemspec->var_name, vstr);
164 				}
165 			}
166 		}
167 		vStringDelete (vstr);
168 	}
169 	return CORK_NIL;
170 }
171 
inputStart(subparser * s)172 static void inputStart (subparser *s)
173 {
174 	struct sGemSpecSubparser *gemspec = (struct sGemSpecSubparser *)s;
175 
176 	gemspec->var_name = vStringNew ();
177 }
178 
inputEnd(subparser * s)179 static void inputEnd (subparser *s)
180 {
181 	struct sGemSpecSubparser *gemspec = (struct sGemSpecSubparser *)s;
182 
183 	vStringDelete (gemspec->var_name); /* NULL is acceptable. */
184 	gemspec->var_name = NULL;
185 }
186 
GemSpecParser(void)187 extern parserDefinition* GemSpecParser (void)
188 {
189 	static const char *const extensions [] = { "gemspec", NULL };
190 	static struct sGemSpecSubparser gemspecSubparser = {
191 		.ruby = {
192 			.subparser = {
193 				.direction = SUBPARSER_SUB_RUNS_BASE,
194 				.inputStart = inputStart,
195 				.inputEnd = inputEnd,
196 			},
197 			.lineNotify = lineNotify,
198 		},
199 		.var_name = NULL,
200 	};
201 
202 	static parserDependency dependencies [] = {
203 		[0] = { DEPTYPE_SUBPARSER, "Ruby", &gemspecSubparser },
204 	};
205 
206 	parserDefinition* const def = parserNew ("GemSpec");
207 
208 	def->dependencies = dependencies;
209 	def->dependencyCount = ARRAY_SIZE(dependencies);
210 	def->kindTable  = GemSpecKinds;
211 	def->kindCount  = ARRAY_SIZE (GemSpecKinds);
212 	def->extensions = extensions;
213 	def->parser     = findGemSpecTags;
214 	def->useCork    = CORK_QUEUE;
215 
216 	return def;
217 }
218