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