nexmon – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /* |
2 | * Copyright © 2010 Codethink Limited |
||
3 | * |
||
4 | * This library is free software; you can redistribute it and/or |
||
5 | * modify it under the terms of the GNU Lesser General Public |
||
6 | * License as published by the Free Software Foundation; either |
||
7 | * version 2 of the licence, or (at your option) any later version. |
||
8 | * |
||
9 | * This library is distributed in the hope that it will be useful, |
||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
12 | * Lesser General Public License for more details. |
||
13 | * |
||
14 | * You should have received a copy of the GNU Lesser General Public |
||
15 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
||
16 | * |
||
17 | * Author: Ryan Lortie <desrt@desrt.ca> |
||
18 | */ |
||
19 | |||
20 | /* Prologue {{{1 */ |
||
21 | #include "config.h" |
||
22 | |||
23 | #include <gstdio.h> |
||
24 | #include <gi18n.h> |
||
25 | |||
26 | #include <string.h> |
||
27 | #include <stdio.h> |
||
28 | #include <locale.h> |
||
29 | |||
30 | #include "gvdb/gvdb-builder.h" |
||
31 | #include "strinfo.c" |
||
32 | |||
33 | #ifdef G_OS_WIN32 |
||
34 | #include "glib/glib-private.h" |
||
35 | #endif |
||
36 | |||
37 | static void |
||
38 | strip_string (GString *string) |
||
39 | { |
||
40 | gint i; |
||
41 | |||
42 | for (i = 0; g_ascii_isspace (string->str[i]); i++); |
||
43 | g_string_erase (string, 0, i); |
||
44 | |||
45 | if (string->len > 0) |
||
46 | { |
||
47 | /* len > 0, so there must be at least one non-whitespace character */ |
||
48 | for (i = string->len - 1; g_ascii_isspace (string->str[i]); i--); |
||
49 | g_string_truncate (string, i + 1); |
||
50 | } |
||
51 | } |
||
52 | |||
53 | /* Handling of <enum> {{{1 */ |
||
54 | typedef struct |
||
55 | { |
||
56 | GString *strinfo; |
||
57 | |||
58 | gboolean is_flags; |
||
59 | } EnumState; |
||
60 | |||
61 | static void |
||
62 | enum_state_free (gpointer data) |
||
63 | { |
||
64 | EnumState *state = data; |
||
65 | |||
66 | g_string_free (state->strinfo, TRUE); |
||
67 | g_slice_free (EnumState, state); |
||
68 | } |
||
69 | |||
70 | static EnumState * |
||
71 | enum_state_new (gboolean is_flags) |
||
72 | { |
||
73 | EnumState *state; |
||
74 | |||
75 | state = g_slice_new (EnumState); |
||
76 | state->strinfo = g_string_new (NULL); |
||
77 | state->is_flags = is_flags; |
||
78 | |||
79 | return state; |
||
80 | } |
||
81 | |||
82 | static void |
||
83 | enum_state_add_value (EnumState *state, |
||
84 | const gchar *nick, |
||
85 | const gchar *valuestr, |
||
86 | GError **error) |
||
87 | { |
||
88 | gint64 value; |
||
89 | gchar *end; |
||
90 | |||
91 | if (nick[0] == '\0' || nick[1] == '\0') |
||
92 | { |
||
93 | g_set_error (error, G_MARKUP_ERROR, |
||
94 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
95 | "nick must be a minimum of 2 characters"); |
||
96 | return; |
||
97 | } |
||
98 | |||
99 | value = g_ascii_strtoll (valuestr, &end, 0); |
||
100 | if (*end || state->is_flags ? |
||
101 | (value > G_MAXUINT32 || value < 0) : |
||
102 | (value > G_MAXINT32 || value < G_MININT32)) |
||
103 | { |
||
104 | g_set_error (error, G_MARKUP_ERROR, |
||
105 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
106 | "invalid numeric value"); |
||
107 | return; |
||
108 | } |
||
109 | |||
110 | if (strinfo_builder_contains (state->strinfo, nick)) |
||
111 | { |
||
112 | g_set_error (error, G_MARKUP_ERROR, |
||
113 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
114 | "<value nick='%s'/> already specified", nick); |
||
115 | return; |
||
116 | } |
||
117 | |||
118 | if (strinfo_builder_contains_value (state->strinfo, value)) |
||
119 | { |
||
120 | g_set_error (error, G_MARKUP_ERROR, |
||
121 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
122 | "value='%s' already specified", valuestr); |
||
123 | return; |
||
124 | } |
||
125 | |||
126 | /* Silently drop the null case if it is mentioned. |
||
127 | * It is properly denoted with an empty array. |
||
128 | */ |
||
129 | if (state->is_flags && value == 0) |
||
130 | return; |
||
131 | |||
132 | if (state->is_flags && (value & (value - 1))) |
||
133 | { |
||
134 | g_set_error (error, G_MARKUP_ERROR, |
||
135 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
136 | "flags values must have at most 1 bit set"); |
||
137 | return; |
||
138 | } |
||
139 | |||
140 | /* Since we reject exact duplicates of value='' and we only allow one |
||
141 | * bit to be set, it's not possible to have overlaps. |
||
142 | * |
||
143 | * If we loosen the one-bit-set restriction we need an overlap check. |
||
144 | */ |
||
145 | |||
146 | strinfo_builder_append_item (state->strinfo, nick, value); |
||
147 | } |
||
148 | |||
149 | static void |
||
150 | enum_state_end (EnumState **state_ptr, |
||
151 | GError **error) |
||
152 | { |
||
153 | EnumState *state; |
||
154 | |||
155 | state = *state_ptr; |
||
156 | *state_ptr = NULL; |
||
157 | |||
158 | if (state->strinfo->len == 0) |
||
159 | g_set_error (error, |
||
160 | G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
161 | "<%s> must contain at least one <value>", |
||
162 | state->is_flags ? "flags" : "enum"); |
||
163 | } |
||
164 | |||
165 | /* Handling of <key> {{{1 */ |
||
166 | typedef struct |
||
167 | { |
||
168 | /* for <child>, @child_schema will be set. |
||
169 | * for <key>, everything else will be set. |
||
170 | */ |
||
171 | gchar *child_schema; |
||
172 | |||
173 | |||
174 | GVariantType *type; |
||
175 | gboolean have_gettext_domain; |
||
176 | |||
177 | gchar l10n; |
||
178 | gchar *l10n_context; |
||
179 | GString *unparsed_default_value; |
||
180 | GVariant *default_value; |
||
181 | |||
182 | GString *strinfo; |
||
183 | gboolean is_enum; |
||
184 | gboolean is_flags; |
||
185 | |||
186 | GVariant *minimum; |
||
187 | GVariant *maximum; |
||
188 | |||
189 | gboolean has_choices; |
||
190 | gboolean has_aliases; |
||
191 | gboolean is_override; |
||
192 | |||
193 | gboolean checked; |
||
194 | GVariant *serialised; |
||
195 | |||
196 | gboolean summary_seen; |
||
197 | gboolean description_seen; |
||
198 | } KeyState; |
||
199 | |||
200 | static KeyState * |
||
201 | key_state_new (const gchar *type_string, |
||
202 | const gchar *gettext_domain, |
||
203 | gboolean is_enum, |
||
204 | gboolean is_flags, |
||
205 | GString *strinfo) |
||
206 | { |
||
207 | KeyState *state; |
||
208 | |||
209 | state = g_slice_new0 (KeyState); |
||
210 | state->type = g_variant_type_new (type_string); |
||
211 | state->have_gettext_domain = gettext_domain != NULL; |
||
212 | state->is_enum = is_enum; |
||
213 | state->is_flags = is_flags; |
||
214 | state->summary_seen = FALSE; |
||
215 | state->description_seen = FALSE; |
||
216 | |||
217 | if (strinfo) |
||
218 | state->strinfo = g_string_new_len (strinfo->str, strinfo->len); |
||
219 | else |
||
220 | state->strinfo = g_string_new (NULL); |
||
221 | |||
222 | return state; |
||
223 | } |
||
224 | |||
225 | static KeyState * |
||
226 | key_state_override (KeyState *state, |
||
227 | const gchar *gettext_domain) |
||
228 | { |
||
229 | KeyState *copy; |
||
230 | |||
231 | copy = g_slice_new0 (KeyState); |
||
232 | copy->type = g_variant_type_copy (state->type); |
||
233 | copy->have_gettext_domain = gettext_domain != NULL; |
||
234 | copy->strinfo = g_string_new_len (state->strinfo->str, |
||
235 | state->strinfo->len); |
||
236 | copy->is_enum = state->is_enum; |
||
237 | copy->is_flags = state->is_flags; |
||
238 | copy->is_override = TRUE; |
||
239 | |||
240 | if (state->minimum) |
||
241 | { |
||
242 | copy->minimum = g_variant_ref (state->minimum); |
||
243 | copy->maximum = g_variant_ref (state->maximum); |
||
244 | } |
||
245 | |||
246 | return copy; |
||
247 | } |
||
248 | |||
249 | static KeyState * |
||
250 | key_state_new_child (const gchar *child_schema) |
||
251 | { |
||
252 | KeyState *state; |
||
253 | |||
254 | state = g_slice_new0 (KeyState); |
||
255 | state->child_schema = g_strdup (child_schema); |
||
256 | |||
257 | return state; |
||
258 | } |
||
259 | |||
260 | static gboolean |
||
261 | is_valid_choices (GVariant *variant, |
||
262 | GString *strinfo) |
||
263 | { |
||
264 | switch (g_variant_classify (variant)) |
||
265 | { |
||
266 | case G_VARIANT_CLASS_MAYBE: |
||
267 | case G_VARIANT_CLASS_ARRAY: |
||
268 | { |
||
269 | gboolean valid = TRUE; |
||
270 | GVariantIter iter; |
||
271 | |||
272 | g_variant_iter_init (&iter, variant); |
||
273 | |||
274 | while (valid && (variant = g_variant_iter_next_value (&iter))) |
||
275 | { |
||
276 | valid = is_valid_choices (variant, strinfo); |
||
277 | g_variant_unref (variant); |
||
278 | } |
||
279 | |||
280 | return valid; |
||
281 | } |
||
282 | |||
283 | case G_VARIANT_CLASS_STRING: |
||
284 | return strinfo_is_string_valid ((const guint32 *) strinfo->str, |
||
285 | strinfo->len / 4, |
||
286 | g_variant_get_string (variant, NULL)); |
||
287 | |||
288 | default: |
||
289 | g_assert_not_reached (); |
||
290 | } |
||
291 | } |
||
292 | |||
293 | |||
294 | /* Gets called at </default> </choices> or <range/> to check for |
||
295 | * validity of the default value so that any inconsistency is |
||
296 | * reported as soon as it is encountered. |
||
297 | */ |
||
298 | static void |
||
299 | key_state_check_range (KeyState *state, |
||
300 | GError **error) |
||
301 | { |
||
302 | if (state->default_value) |
||
303 | { |
||
304 | const gchar *tag; |
||
305 | |||
306 | tag = state->is_override ? "override" : "default"; |
||
307 | |||
308 | if (state->minimum) |
||
309 | { |
||
310 | if (g_variant_compare (state->default_value, state->minimum) < 0 || |
||
311 | g_variant_compare (state->default_value, state->maximum) > 0) |
||
312 | { |
||
313 | g_set_error (error, G_MARKUP_ERROR, |
||
314 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
315 | "<%s> is not contained in " |
||
316 | "the specified range", tag); |
||
317 | } |
||
318 | } |
||
319 | |||
320 | else if (state->strinfo->len) |
||
321 | { |
||
322 | if (!is_valid_choices (state->default_value, state->strinfo)) |
||
323 | { |
||
324 | if (state->is_enum) |
||
325 | g_set_error (error, G_MARKUP_ERROR, |
||
326 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
327 | "<%s> is not a valid member of " |
||
328 | "the specified enumerated type", tag); |
||
329 | |||
330 | else if (state->is_flags) |
||
331 | g_set_error (error, G_MARKUP_ERROR, |
||
332 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
333 | "<%s> contains string not in the " |
||
334 | "specified flags type", tag); |
||
335 | |||
336 | else |
||
337 | g_set_error (error, G_MARKUP_ERROR, |
||
338 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
339 | "<%s> contains string not in " |
||
340 | "<choices>", tag); |
||
341 | } |
||
342 | } |
||
343 | } |
||
344 | } |
||
345 | |||
346 | static void |
||
347 | key_state_set_range (KeyState *state, |
||
348 | const gchar *min_str, |
||
349 | const gchar *max_str, |
||
350 | GError **error) |
||
351 | { |
||
352 | const struct { |
||
353 | const gchar type; |
||
354 | const gchar *min; |
||
355 | const gchar *max; |
||
356 | } table[] = { |
||
357 | { 'y', "0", "255" }, |
||
358 | { 'n', "-32768", "32767" }, |
||
359 | { 'q', "0", "65535" }, |
||
360 | { 'i', "-2147483648", "2147483647" }, |
||
361 | { 'u', "0", "4294967295" }, |
||
362 | { 'x', "-9223372036854775808", "9223372036854775807" }, |
||
363 | { 't', "0", "18446744073709551615" }, |
||
364 | { 'd', "-inf", "inf" }, |
||
365 | }; |
||
366 | gboolean type_ok = FALSE; |
||
367 | gint i; |
||
368 | |||
369 | if (state->minimum) |
||
370 | { |
||
371 | g_set_error_literal (error, G_MARKUP_ERROR, |
||
372 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
373 | "<range/> already specified for this key"); |
||
374 | return; |
||
375 | } |
||
376 | |||
377 | for (i = 0; i < G_N_ELEMENTS (table); i++) |
||
378 | if (*(char *) state->type == table[i].type) |
||
379 | { |
||
380 | min_str = min_str ? min_str : table[i].min; |
||
381 | max_str = max_str ? max_str : table[i].max; |
||
382 | type_ok = TRUE; |
||
383 | break; |
||
384 | } |
||
385 | |||
386 | if (!type_ok) |
||
387 | { |
||
388 | gchar *type = g_variant_type_dup_string (state->type); |
||
389 | g_set_error (error, G_MARKUP_ERROR, |
||
390 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
391 | "<range> not allowed for keys of type '%s'", type); |
||
392 | g_free (type); |
||
393 | return; |
||
394 | } |
||
395 | |||
396 | state->minimum = g_variant_parse (state->type, min_str, NULL, NULL, error); |
||
397 | if (state->minimum == NULL) |
||
398 | return; |
||
399 | |||
400 | state->maximum = g_variant_parse (state->type, max_str, NULL, NULL, error); |
||
401 | if (state->maximum == NULL) |
||
402 | return; |
||
403 | |||
404 | if (g_variant_compare (state->minimum, state->maximum) > 0) |
||
405 | { |
||
406 | g_set_error (error, G_MARKUP_ERROR, |
||
407 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
408 | "<range> specified minimum is greater than maxmimum"); |
||
409 | return; |
||
410 | } |
||
411 | |||
412 | key_state_check_range (state, error); |
||
413 | } |
||
414 | |||
415 | static GString * |
||
416 | key_state_start_default (KeyState *state, |
||
417 | const gchar *l10n, |
||
418 | const gchar *context, |
||
419 | GError **error) |
||
420 | { |
||
421 | if (l10n != NULL) |
||
422 | { |
||
423 | if (strcmp (l10n, "messages") == 0) |
||
424 | state->l10n = 'm'; |
||
425 | |||
426 | else if (strcmp (l10n, "time") == 0) |
||
427 | state->l10n = 't'; |
||
428 | |||
429 | else |
||
430 | { |
||
431 | g_set_error (error, G_MARKUP_ERROR, |
||
432 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
433 | "unsupported l10n category: %s", l10n); |
||
434 | return NULL; |
||
435 | } |
||
436 | |||
437 | if (!state->have_gettext_domain) |
||
438 | { |
||
439 | g_set_error_literal (error, G_MARKUP_ERROR, |
||
440 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
441 | "l10n requested, but no " |
||
442 | "gettext domain given"); |
||
443 | return NULL; |
||
444 | } |
||
445 | |||
446 | state->l10n_context = g_strdup (context); |
||
447 | } |
||
448 | |||
449 | else if (context != NULL) |
||
450 | { |
||
451 | g_set_error_literal (error, G_MARKUP_ERROR, |
||
452 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
453 | "translation context given for " |
||
454 | " value without l10n enabled"); |
||
455 | return NULL; |
||
456 | } |
||
457 | |||
458 | return g_string_new (NULL); |
||
459 | } |
||
460 | |||
461 | static void |
||
462 | key_state_end_default (KeyState *state, |
||
463 | GString **string, |
||
464 | GError **error) |
||
465 | { |
||
466 | state->unparsed_default_value = *string; |
||
467 | *string = NULL; |
||
468 | |||
469 | state->default_value = g_variant_parse (state->type, |
||
470 | state->unparsed_default_value->str, |
||
471 | NULL, NULL, error); |
||
472 | if (!state->default_value) |
||
473 | { |
||
474 | gchar *type = g_variant_type_dup_string (state->type); |
||
475 | g_prefix_error (error, "failed to parse <default> value of type '%s': ", type); |
||
476 | g_free (type); |
||
477 | } |
||
478 | |||
479 | key_state_check_range (state, error); |
||
480 | } |
||
481 | |||
482 | static void |
||
483 | key_state_start_choices (KeyState *state, |
||
484 | GError **error) |
||
485 | { |
||
486 | const GVariantType *type = state->type; |
||
487 | |||
488 | if (state->is_enum) |
||
489 | { |
||
490 | g_set_error_literal (error, G_MARKUP_ERROR, |
||
491 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
492 | "<choices> cannot be specified for keys " |
||
493 | "tagged as having an enumerated type"); |
||
494 | return; |
||
495 | } |
||
496 | |||
497 | if (state->has_choices) |
||
498 | { |
||
499 | g_set_error_literal (error, G_MARKUP_ERROR, |
||
500 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
501 | "<choices> already specified for this key"); |
||
502 | return; |
||
503 | } |
||
504 | |||
505 | while (g_variant_type_is_maybe (type) || g_variant_type_is_array (type)) |
||
506 | type = g_variant_type_element (type); |
||
507 | |||
508 | if (!g_variant_type_equal (type, G_VARIANT_TYPE_STRING)) |
||
509 | { |
||
510 | gchar *type_string = g_variant_type_dup_string (state->type); |
||
511 | g_set_error (error, G_MARKUP_ERROR, |
||
512 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
513 | "<choices> not allowed for keys of type '%s'", |
||
514 | type_string); |
||
515 | g_free (type_string); |
||
516 | return; |
||
517 | } |
||
518 | } |
||
519 | |||
520 | static void |
||
521 | key_state_add_choice (KeyState *state, |
||
522 | const gchar *choice, |
||
523 | GError **error) |
||
524 | { |
||
525 | if (strinfo_builder_contains (state->strinfo, choice)) |
||
526 | { |
||
527 | g_set_error (error, G_MARKUP_ERROR, |
||
528 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
529 | "<choice value='%s'/> already given", choice); |
||
530 | return; |
||
531 | } |
||
532 | |||
533 | strinfo_builder_append_item (state->strinfo, choice, 0); |
||
534 | state->has_choices = TRUE; |
||
535 | } |
||
536 | |||
537 | static void |
||
538 | key_state_end_choices (KeyState *state, |
||
539 | GError **error) |
||
540 | { |
||
541 | if (!state->has_choices) |
||
542 | { |
||
543 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
544 | "<choices> must contain at least one <choice>"); |
||
545 | return; |
||
546 | } |
||
547 | |||
548 | key_state_check_range (state, error); |
||
549 | } |
||
550 | |||
551 | static void |
||
552 | key_state_start_aliases (KeyState *state, |
||
553 | GError **error) |
||
554 | { |
||
555 | if (state->has_aliases) |
||
556 | g_set_error_literal (error, G_MARKUP_ERROR, |
||
557 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
558 | "<aliases> already specified for this key"); |
||
559 | else if (!state->is_flags && !state->is_enum && !state->has_choices) |
||
560 | g_set_error_literal (error, G_MARKUP_ERROR, |
||
561 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
562 | "<aliases> can only be specified for keys with " |
||
563 | "enumerated or flags types or after <choices>"); |
||
564 | } |
||
565 | |||
566 | static void |
||
567 | key_state_add_alias (KeyState *state, |
||
568 | const gchar *alias, |
||
569 | const gchar *target, |
||
570 | GError **error) |
||
571 | { |
||
572 | if (strinfo_builder_contains (state->strinfo, alias)) |
||
573 | { |
||
574 | if (strinfo_is_string_valid ((guint32 *) state->strinfo->str, |
||
575 | state->strinfo->len / 4, |
||
576 | alias)) |
||
577 | { |
||
578 | if (state->is_enum) |
||
579 | g_set_error (error, G_MARKUP_ERROR, |
||
580 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
581 | "<alias value='%s'/> given when '%s' is already " |
||
582 | "a member of the enumerated type", alias, alias); |
||
583 | |||
584 | else |
||
585 | g_set_error (error, G_MARKUP_ERROR, |
||
586 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
587 | "<alias value='%s'/> given when " |
||
588 | "<choice value='%s'/> was already given", |
||
589 | alias, alias); |
||
590 | } |
||
591 | |||
592 | else |
||
593 | g_set_error (error, G_MARKUP_ERROR, |
||
594 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
595 | "<alias value='%s'/> already specified", alias); |
||
596 | |||
597 | return; |
||
598 | } |
||
599 | |||
600 | if (!strinfo_builder_append_alias (state->strinfo, alias, target)) |
||
601 | { |
||
602 | g_set_error (error, G_MARKUP_ERROR, |
||
603 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
604 | "alias target '%s' is not in %s", target, |
||
605 | state->is_enum ? "enumerated type" : "<choices>"); |
||
606 | return; |
||
607 | } |
||
608 | |||
609 | state->has_aliases = TRUE; |
||
610 | } |
||
611 | |||
612 | static void |
||
613 | key_state_end_aliases (KeyState *state, |
||
614 | GError **error) |
||
615 | { |
||
616 | if (!state->has_aliases) |
||
617 | { |
||
618 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
619 | "<aliases> must contain at least one <alias>"); |
||
620 | return; |
||
621 | } |
||
622 | } |
||
623 | |||
624 | static gboolean |
||
625 | key_state_check (KeyState *state, |
||
626 | GError **error) |
||
627 | { |
||
628 | if (state->checked) |
||
629 | return TRUE; |
||
630 | |||
631 | return state->checked = TRUE; |
||
632 | } |
||
633 | |||
634 | static GVariant * |
||
635 | key_state_serialise (KeyState *state) |
||
636 | { |
||
637 | if (state->serialised == NULL) |
||
638 | { |
||
639 | if (state->child_schema) |
||
640 | { |
||
641 | state->serialised = g_variant_new_string (state->child_schema); |
||
642 | } |
||
643 | |||
644 | else |
||
645 | { |
||
646 | GVariantBuilder builder; |
||
647 | |||
648 | g_assert (key_state_check (state, NULL)); |
||
649 | |||
650 | g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE); |
||
651 | |||
652 | /* default value */ |
||
653 | g_variant_builder_add_value (&builder, state->default_value); |
||
654 | |||
655 | /* translation */ |
||
656 | if (state->l10n) |
||
657 | { |
||
658 | /* We are going to store the untranslated default for |
||
659 | * runtime translation according to the current locale. |
||
660 | * We need to strip leading and trailing whitespace from |
||
661 | * the string so that it's exactly the same as the one |
||
662 | * that ended up in the .po file for translation. |
||
663 | * |
||
664 | * We want to do this so that |
||
665 | * |
||
666 | * <default l10n='messages'> |
||
667 | * ['a', 'b', 'c'] |
||
668 | * </default> |
||
669 | * |
||
670 | * ends up in the .po file like "['a', 'b', 'c']", |
||
671 | * omitting the extra whitespace at the start and end. |
||
672 | */ |
||
673 | strip_string (state->unparsed_default_value); |
||
674 | |||
675 | if (state->l10n_context) |
||
676 | { |
||
677 | gint len; |
||
678 | |||
679 | /* Contextified messages are supported by prepending |
||
680 | * the context, followed by '\004' to the start of the |
||
681 | * message string. We do that here to save GSettings |
||
682 | * the work later on. |
||
683 | */ |
||
684 | len = strlen (state->l10n_context); |
||
685 | state->l10n_context[len] = '\004'; |
||
686 | g_string_prepend_len (state->unparsed_default_value, |
||
687 | state->l10n_context, len + 1); |
||
688 | g_free (state->l10n_context); |
||
689 | state->l10n_context = NULL; |
||
690 | } |
||
691 | |||
692 | g_variant_builder_add (&builder, "(y(y&s))", 'l', state->l10n, |
||
693 | state->unparsed_default_value->str); |
||
694 | g_string_free (state->unparsed_default_value, TRUE); |
||
695 | state->unparsed_default_value = NULL; |
||
696 | } |
||
697 | |||
698 | /* choice, aliases, enums */ |
||
699 | if (state->strinfo->len) |
||
700 | { |
||
701 | GVariant *array; |
||
702 | guint32 *words; |
||
703 | gpointer data; |
||
704 | gsize size; |
||
705 | gint i; |
||
706 | |||
707 | data = state->strinfo->str; |
||
708 | size = state->strinfo->len; |
||
709 | |||
710 | words = data; |
||
711 | for (i = 0; i < size / sizeof (guint32); i++) |
||
712 | words[i] = GUINT32_TO_LE (words[i]); |
||
713 | |||
714 | array = g_variant_new_from_data (G_VARIANT_TYPE ("au"), |
||
715 | data, size, TRUE, |
||
716 | g_free, data); |
||
717 | |||
718 | g_string_free (state->strinfo, FALSE); |
||
719 | state->strinfo = NULL; |
||
720 | |||
721 | g_variant_builder_add (&builder, "(y@au)", |
||
722 | state->is_flags ? 'f' : |
||
723 | state->is_enum ? 'e' : 'c', |
||
724 | array); |
||
725 | } |
||
726 | |||
727 | /* range */ |
||
728 | if (state->minimum || state->maximum) |
||
729 | g_variant_builder_add (&builder, "(y(**))", 'r', |
||
730 | state->minimum, state->maximum); |
||
731 | |||
732 | state->serialised = g_variant_builder_end (&builder); |
||
733 | } |
||
734 | |||
735 | g_variant_ref_sink (state->serialised); |
||
736 | } |
||
737 | |||
738 | return g_variant_ref (state->serialised); |
||
739 | } |
||
740 | |||
741 | static void |
||
742 | key_state_free (gpointer data) |
||
743 | { |
||
744 | KeyState *state = data; |
||
745 | |||
746 | if (state->type) |
||
747 | g_variant_type_free (state->type); |
||
748 | |||
749 | g_free (state->l10n_context); |
||
750 | |||
751 | if (state->unparsed_default_value) |
||
752 | g_string_free (state->unparsed_default_value, TRUE); |
||
753 | |||
754 | if (state->default_value) |
||
755 | g_variant_unref (state->default_value); |
||
756 | |||
757 | if (state->strinfo) |
||
758 | g_string_free (state->strinfo, TRUE); |
||
759 | |||
760 | if (state->minimum) |
||
761 | g_variant_unref (state->minimum); |
||
762 | |||
763 | if (state->maximum) |
||
764 | g_variant_unref (state->maximum); |
||
765 | |||
766 | if (state->serialised) |
||
767 | g_variant_unref (state->serialised); |
||
768 | |||
769 | g_slice_free (KeyState, state); |
||
770 | } |
||
771 | |||
772 | /* Key name validity {{{1 */ |
||
773 | static gboolean allow_any_name = FALSE; |
||
774 | |||
775 | static gboolean |
||
776 | is_valid_keyname (const gchar *key, |
||
777 | GError **error) |
||
778 | { |
||
779 | gint i; |
||
780 | |||
781 | if (key[0] == '\0') |
||
782 | { |
||
783 | g_set_error_literal (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
784 | _("empty names are not permitted")); |
||
785 | return FALSE; |
||
786 | } |
||
787 | |||
788 | if (allow_any_name) |
||
789 | return TRUE; |
||
790 | |||
791 | if (!g_ascii_islower (key[0])) |
||
792 | { |
||
793 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
794 | _("invalid name '%s': names must begin " |
||
795 | "with a lowercase letter"), key); |
||
796 | return FALSE; |
||
797 | } |
||
798 | |||
799 | for (i = 1; key[i]; i++) |
||
800 | { |
||
801 | if (key[i] != '-' && |
||
802 | !g_ascii_islower (key[i]) && |
||
803 | !g_ascii_isdigit (key[i])) |
||
804 | { |
||
805 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
806 | _("invalid name '%s': invalid character '%c'; " |
||
807 | "only lowercase letters, numbers and hyphen ('-') " |
||
808 | "are permitted."), key, key[i]); |
||
809 | return FALSE; |
||
810 | } |
||
811 | |||
812 | if (key[i] == '-' && key[i + 1] == '-') |
||
813 | { |
||
814 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
815 | _("invalid name '%s': two successive hyphens ('--') " |
||
816 | "are not permitted."), key); |
||
817 | return FALSE; |
||
818 | } |
||
819 | } |
||
820 | |||
821 | if (key[i - 1] == '-') |
||
822 | { |
||
823 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
824 | _("invalid name '%s': the last character may not be a " |
||
825 | "hyphen ('-')."), key); |
||
826 | return FALSE; |
||
827 | } |
||
828 | |||
829 | if (i > 1024) |
||
830 | { |
||
831 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
832 | _("invalid name '%s': maximum length is 1024"), key); |
||
833 | return FALSE; |
||
834 | } |
||
835 | |||
836 | return TRUE; |
||
837 | } |
||
838 | |||
839 | /* Handling of <schema> {{{1 */ |
||
840 | typedef struct _SchemaState SchemaState; |
||
841 | struct _SchemaState |
||
842 | { |
||
843 | SchemaState *extends; |
||
844 | |||
845 | gchar *path; |
||
846 | gchar *gettext_domain; |
||
847 | gchar *extends_name; |
||
848 | gchar *list_of; |
||
849 | |||
850 | GHashTable *keys; |
||
851 | }; |
||
852 | |||
853 | static SchemaState * |
||
854 | schema_state_new (const gchar *path, |
||
855 | const gchar *gettext_domain, |
||
856 | SchemaState *extends, |
||
857 | const gchar *extends_name, |
||
858 | const gchar *list_of) |
||
859 | { |
||
860 | SchemaState *state; |
||
861 | |||
862 | state = g_slice_new (SchemaState); |
||
863 | state->path = g_strdup (path); |
||
864 | state->gettext_domain = g_strdup (gettext_domain); |
||
865 | state->extends = extends; |
||
866 | state->extends_name = g_strdup (extends_name); |
||
867 | state->list_of = g_strdup (list_of); |
||
868 | state->keys = g_hash_table_new_full (g_str_hash, g_str_equal, |
||
869 | g_free, key_state_free); |
||
870 | |||
871 | return state; |
||
872 | } |
||
873 | |||
874 | static void |
||
875 | schema_state_free (gpointer data) |
||
876 | { |
||
877 | SchemaState *state = data; |
||
878 | |||
879 | g_free (state->path); |
||
880 | g_free (state->gettext_domain); |
||
881 | g_hash_table_unref (state->keys); |
||
882 | } |
||
883 | |||
884 | static void |
||
885 | schema_state_add_child (SchemaState *state, |
||
886 | const gchar *name, |
||
887 | const gchar *schema, |
||
888 | GError **error) |
||
889 | { |
||
890 | gchar *childname; |
||
891 | |||
892 | if (!is_valid_keyname (name, error)) |
||
893 | return; |
||
894 | |||
895 | childname = g_strconcat (name, "/", NULL); |
||
896 | |||
897 | if (g_hash_table_lookup (state->keys, childname)) |
||
898 | { |
||
899 | g_set_error (error, G_MARKUP_ERROR, |
||
900 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
901 | _("<child name='%s'> already specified"), name); |
||
902 | return; |
||
903 | } |
||
904 | |||
905 | g_hash_table_insert (state->keys, childname, |
||
906 | key_state_new_child (schema)); |
||
907 | } |
||
908 | |||
909 | static KeyState * |
||
910 | schema_state_add_key (SchemaState *state, |
||
911 | GHashTable *enum_table, |
||
912 | GHashTable *flags_table, |
||
913 | const gchar *name, |
||
914 | const gchar *type_string, |
||
915 | const gchar *enum_type, |
||
916 | const gchar *flags_type, |
||
917 | GError **error) |
||
918 | { |
||
919 | SchemaState *node; |
||
920 | GString *strinfo; |
||
921 | KeyState *key; |
||
922 | |||
923 | if (state->list_of) |
||
924 | { |
||
925 | g_set_error_literal (error, G_MARKUP_ERROR, |
||
926 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
927 | _("cannot add keys to a 'list-of' schema")); |
||
928 | return NULL; |
||
929 | } |
||
930 | |||
931 | if (!is_valid_keyname (name, error)) |
||
932 | return NULL; |
||
933 | |||
934 | if (g_hash_table_lookup (state->keys, name)) |
||
935 | { |
||
936 | g_set_error (error, G_MARKUP_ERROR, |
||
937 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
938 | _("<key name='%s'> already specified"), name); |
||
939 | return NULL; |
||
940 | } |
||
941 | |||
942 | for (node = state; node; node = node->extends) |
||
943 | if (node->extends) |
||
944 | { |
||
945 | KeyState *shadow; |
||
946 | |||
947 | shadow = g_hash_table_lookup (node->extends->keys, name); |
||
948 | |||
949 | /* in case of <key> <override> <key> make sure we report the |
||
950 | * location of the original <key>, not the <override>. |
||
951 | */ |
||
952 | if (shadow && !shadow->is_override) |
||
953 | { |
||
954 | g_set_error (error, G_MARKUP_ERROR, |
||
955 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
956 | _("<key name='%s'> shadows <key name='%s'> in " |
||
957 | "<schema id='%s'>; use <override> to modify value"), |
||
958 | name, name, node->extends_name); |
||
959 | return NULL; |
||
960 | } |
||
961 | } |
||
962 | |||
963 | if ((type_string != NULL) + (enum_type != NULL) + (flags_type != NULL) != 1) |
||
964 | { |
||
965 | g_set_error (error, G_MARKUP_ERROR, |
||
966 | G_MARKUP_ERROR_MISSING_ATTRIBUTE, |
||
967 | _("exactly one of 'type', 'enum' or 'flags' must " |
||
968 | "be specified as an attribute to <key>")); |
||
969 | return NULL; |
||
970 | } |
||
971 | |||
972 | if (type_string == NULL) /* flags or enums was specified */ |
||
973 | { |
||
974 | EnumState *enum_state; |
||
975 | |||
976 | if (enum_type) |
||
977 | enum_state = g_hash_table_lookup (enum_table, enum_type); |
||
978 | else |
||
979 | enum_state = g_hash_table_lookup (flags_table, flags_type); |
||
980 | |||
981 | |||
982 | if (enum_state == NULL) |
||
983 | { |
||
984 | g_set_error (error, G_MARKUP_ERROR, |
||
985 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
986 | _("<%s id='%s'> not (yet) defined."), |
||
987 | flags_type ? "flags" : "enum", |
||
988 | flags_type ? flags_type : enum_type); |
||
989 | return NULL; |
||
990 | } |
||
991 | |||
992 | type_string = flags_type ? "as" : "s"; |
||
993 | strinfo = enum_state->strinfo; |
||
994 | } |
||
995 | else |
||
996 | { |
||
997 | if (!g_variant_type_string_is_valid (type_string)) |
||
998 | { |
||
999 | g_set_error (error, G_MARKUP_ERROR, |
||
1000 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
1001 | _("invalid GVariant type string '%s'"), type_string); |
||
1002 | return NULL; |
||
1003 | } |
||
1004 | |||
1005 | strinfo = NULL; |
||
1006 | } |
||
1007 | |||
1008 | key = key_state_new (type_string, state->gettext_domain, |
||
1009 | enum_type != NULL, flags_type != NULL, strinfo); |
||
1010 | g_hash_table_insert (state->keys, g_strdup (name), key); |
||
1011 | |||
1012 | return key; |
||
1013 | } |
||
1014 | |||
1015 | static void |
||
1016 | schema_state_add_override (SchemaState *state, |
||
1017 | KeyState **key_state, |
||
1018 | GString **string, |
||
1019 | const gchar *key, |
||
1020 | const gchar *l10n, |
||
1021 | const gchar *context, |
||
1022 | GError **error) |
||
1023 | { |
||
1024 | SchemaState *parent; |
||
1025 | KeyState *original; |
||
1026 | |||
1027 | if (state->extends == NULL) |
||
1028 | { |
||
1029 | g_set_error_literal (error, G_MARKUP_ERROR, |
||
1030 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
1031 | _("<override> given but schema isn't " |
||
1032 | "extending anything")); |
||
1033 | return; |
||
1034 | } |
||
1035 | |||
1036 | for (parent = state->extends; parent; parent = parent->extends) |
||
1037 | if ((original = g_hash_table_lookup (parent->keys, key))) |
||
1038 | break; |
||
1039 | |||
1040 | if (original == NULL) |
||
1041 | { |
||
1042 | g_set_error (error, G_MARKUP_ERROR, |
||
1043 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
1044 | _("no <key name='%s'> to override"), key); |
||
1045 | return; |
||
1046 | } |
||
1047 | |||
1048 | if (g_hash_table_lookup (state->keys, key)) |
||
1049 | { |
||
1050 | g_set_error (error, G_MARKUP_ERROR, |
||
1051 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
1052 | _("<override name='%s'> already specified"), key); |
||
1053 | return; |
||
1054 | } |
||
1055 | |||
1056 | *key_state = key_state_override (original, state->gettext_domain); |
||
1057 | *string = key_state_start_default (*key_state, l10n, context, error); |
||
1058 | g_hash_table_insert (state->keys, g_strdup (key), *key_state); |
||
1059 | } |
||
1060 | |||
1061 | static void |
||
1062 | override_state_end (KeyState **key_state, |
||
1063 | GString **string, |
||
1064 | GError **error) |
||
1065 | { |
||
1066 | key_state_end_default (*key_state, string, error); |
||
1067 | *key_state = NULL; |
||
1068 | } |
||
1069 | |||
1070 | /* Handling of toplevel state {{{1 */ |
||
1071 | typedef struct |
||
1072 | { |
||
1073 | gboolean strict; /* TRUE if --strict was given */ |
||
1074 | |||
1075 | GHashTable *schema_table; /* string -> SchemaState */ |
||
1076 | GHashTable *flags_table; /* string -> EnumState */ |
||
1077 | GHashTable *enum_table; /* string -> EnumState */ |
||
1078 | |||
1079 | GSList *this_file_schemas; /* strings: <schema>s in this file */ |
||
1080 | GSList *this_file_flagss; /* strings: <flags>s in this file */ |
||
1081 | GSList *this_file_enums; /* strings: <enum>s in this file */ |
||
1082 | |||
1083 | gchar *schemalist_domain; /* the <schemalist> gettext domain */ |
||
1084 | |||
1085 | SchemaState *schema_state; /* non-NULL when inside <schema> */ |
||
1086 | KeyState *key_state; /* non-NULL when inside <key> */ |
||
1087 | EnumState *enum_state; /* non-NULL when inside <enum> */ |
||
1088 | |||
1089 | GString *string; /* non-NULL when accepting text */ |
||
1090 | } ParseState; |
||
1091 | |||
1092 | static gboolean |
||
1093 | is_subclass (const gchar *class_name, |
||
1094 | const gchar *possible_parent, |
||
1095 | GHashTable *schema_table) |
||
1096 | { |
||
1097 | SchemaState *class; |
||
1098 | |||
1099 | if (strcmp (class_name, possible_parent) == 0) |
||
1100 | return TRUE; |
||
1101 | |||
1102 | class = g_hash_table_lookup (schema_table, class_name); |
||
1103 | g_assert (class != NULL); |
||
1104 | |||
1105 | return class->extends_name && |
||
1106 | is_subclass (class->extends_name, possible_parent, schema_table); |
||
1107 | } |
||
1108 | |||
1109 | static void |
||
1110 | parse_state_start_schema (ParseState *state, |
||
1111 | const gchar *id, |
||
1112 | const gchar *path, |
||
1113 | const gchar *gettext_domain, |
||
1114 | const gchar *extends_name, |
||
1115 | const gchar *list_of, |
||
1116 | GError **error) |
||
1117 | { |
||
1118 | SchemaState *extends; |
||
1119 | gchar *my_id; |
||
1120 | |||
1121 | if (g_hash_table_lookup (state->schema_table, id)) |
||
1122 | { |
||
1123 | g_set_error (error, G_MARKUP_ERROR, |
||
1124 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
1125 | _("<schema id='%s'> already specified"), id); |
||
1126 | return; |
||
1127 | } |
||
1128 | |||
1129 | if (extends_name) |
||
1130 | { |
||
1131 | extends = g_hash_table_lookup (state->schema_table, extends_name); |
||
1132 | |||
1133 | if (extends == NULL) |
||
1134 | { |
||
1135 | g_set_error (error, G_MARKUP_ERROR, |
||
1136 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
1137 | _("<schema id='%s'> extends not yet existing " |
||
1138 | "schema '%s'"), id, extends_name); |
||
1139 | return; |
||
1140 | } |
||
1141 | } |
||
1142 | else |
||
1143 | extends = NULL; |
||
1144 | |||
1145 | if (list_of) |
||
1146 | { |
||
1147 | SchemaState *tmp; |
||
1148 | |||
1149 | if (!(tmp = g_hash_table_lookup (state->schema_table, list_of))) |
||
1150 | { |
||
1151 | g_set_error (error, G_MARKUP_ERROR, |
||
1152 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
1153 | _("<schema id='%s'> is list of not yet existing " |
||
1154 | "schema '%s'"), id, list_of); |
||
1155 | return; |
||
1156 | } |
||
1157 | |||
1158 | if (tmp->path) |
||
1159 | { |
||
1160 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
1161 | _("Can not be a list of a schema with a path")); |
||
1162 | return; |
||
1163 | } |
||
1164 | } |
||
1165 | |||
1166 | if (extends) |
||
1167 | { |
||
1168 | if (extends->path) |
||
1169 | { |
||
1170 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
1171 | _("Can not extend a schema with a path")); |
||
1172 | return; |
||
1173 | } |
||
1174 | |||
1175 | if (list_of) |
||
1176 | { |
||
1177 | if (extends->list_of == NULL) |
||
1178 | { |
||
1179 | g_set_error (error, G_MARKUP_ERROR, |
||
1180 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
1181 | _("<schema id='%s'> is a list, extending " |
||
1182 | "<schema id='%s'> which is not a list"), |
||
1183 | id, extends_name); |
||
1184 | return; |
||
1185 | } |
||
1186 | |||
1187 | if (!is_subclass (list_of, extends->list_of, state->schema_table)) |
||
1188 | { |
||
1189 | g_set_error (error, G_MARKUP_ERROR, |
||
1190 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
1191 | _("<schema id='%s' list-of='%s'> extends <schema " |
||
1192 | "id='%s' list-of='%s'> but '%s' does not " |
||
1193 | "extend '%s'"), id, list_of, extends_name, |
||
1194 | extends->list_of, list_of, extends->list_of); |
||
1195 | return; |
||
1196 | } |
||
1197 | } |
||
1198 | else |
||
1199 | /* by default we are a list of the same thing that the schema |
||
1200 | * we are extending is a list of (which might be nothing) |
||
1201 | */ |
||
1202 | list_of = extends->list_of; |
||
1203 | } |
||
1204 | |||
1205 | if (path && !(g_str_has_prefix (path, "/") && g_str_has_suffix (path, "/"))) |
||
1206 | { |
||
1207 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
1208 | _("a path, if given, must begin and end with a slash")); |
||
1209 | return; |
||
1210 | } |
||
1211 | |||
1212 | if (path && list_of && !g_str_has_suffix (path, ":/")) |
||
1213 | { |
||
1214 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
1215 | _("the path of a list must end with ':/'")); |
||
1216 | return; |
||
1217 | } |
||
1218 | |||
1219 | if (path && (g_str_has_prefix (path, "/apps/") || |
||
1220 | g_str_has_prefix (path, "/desktop/") || |
||
1221 | g_str_has_prefix (path, "/system/"))) |
||
1222 | g_printerr ("warning: Schema '%s' has path '%s'. Paths starting with " |
||
1223 | "'/apps/', '/desktop/' or '/system/' are deprecated.\n", id, path); |
||
1224 | |||
1225 | state->schema_state = schema_state_new (path, gettext_domain, |
||
1226 | extends, extends_name, list_of); |
||
1227 | |||
1228 | my_id = g_strdup (id); |
||
1229 | state->this_file_schemas = g_slist_prepend (state->this_file_schemas, my_id); |
||
1230 | g_hash_table_insert (state->schema_table, my_id, state->schema_state); |
||
1231 | } |
||
1232 | |||
1233 | static void |
||
1234 | parse_state_start_enum (ParseState *state, |
||
1235 | const gchar *id, |
||
1236 | gboolean is_flags, |
||
1237 | GError **error) |
||
1238 | { |
||
1239 | GSList **list = is_flags ? &state->this_file_flagss : &state->this_file_enums; |
||
1240 | GHashTable *table = is_flags ? state->flags_table : state->enum_table; |
||
1241 | gchar *my_id; |
||
1242 | |||
1243 | if (g_hash_table_lookup (table, id)) |
||
1244 | { |
||
1245 | g_set_error (error, G_MARKUP_ERROR, |
||
1246 | G_MARKUP_ERROR_INVALID_CONTENT, |
||
1247 | _("<%s id='%s'> already specified"), |
||
1248 | is_flags ? "flags" : "enum", id); |
||
1249 | return; |
||
1250 | } |
||
1251 | |||
1252 | state->enum_state = enum_state_new (is_flags); |
||
1253 | |||
1254 | my_id = g_strdup (id); |
||
1255 | *list = g_slist_prepend (*list, my_id); |
||
1256 | g_hash_table_insert (table, my_id, state->enum_state); |
||
1257 | } |
||
1258 | |||
1259 | /* GMarkup Parser Functions {{{1 */ |
||
1260 | |||
1261 | /* Start element {{{2 */ |
||
1262 | static void |
||
1263 | start_element (GMarkupParseContext *context, |
||
1264 | const gchar *element_name, |
||
1265 | const gchar **attribute_names, |
||
1266 | const gchar **attribute_values, |
||
1267 | gpointer user_data, |
||
1268 | GError **error) |
||
1269 | { |
||
1270 | ParseState *state = user_data; |
||
1271 | const GSList *element_stack; |
||
1272 | const gchar *container; |
||
1273 | |||
1274 | element_stack = g_markup_parse_context_get_element_stack (context); |
||
1275 | container = element_stack->next ? element_stack->next->data : NULL; |
||
1276 | |||
1277 | #define COLLECT(first, ...) \ |
||
1278 | g_markup_collect_attributes (element_name, \ |
||
1279 | attribute_names, attribute_values, error, \ |
||
1280 | first, __VA_ARGS__, G_MARKUP_COLLECT_INVALID) |
||
1281 | #define OPTIONAL G_MARKUP_COLLECT_OPTIONAL |
||
1282 | #define STRDUP G_MARKUP_COLLECT_STRDUP |
||
1283 | #define STRING G_MARKUP_COLLECT_STRING |
||
1284 | #define NO_ATTRS() COLLECT (G_MARKUP_COLLECT_INVALID, NULL) |
||
1285 | |||
1286 | /* Toplevel items {{{3 */ |
||
1287 | if (container == NULL) |
||
1288 | { |
||
1289 | if (strcmp (element_name, "schemalist") == 0) |
||
1290 | { |
||
1291 | COLLECT (OPTIONAL | STRDUP, |
||
1292 | "gettext-domain", |
||
1293 | &state->schemalist_domain); |
||
1294 | return; |
||
1295 | } |
||
1296 | } |
||
1297 | |||
1298 | |||
1299 | /* children of <schemalist> {{{3 */ |
||
1300 | else if (strcmp (container, "schemalist") == 0) |
||
1301 | { |
||
1302 | if (strcmp (element_name, "schema") == 0) |
||
1303 | { |
||
1304 | const gchar *id, *path, *gettext_domain, *extends, *list_of; |
||
1305 | if (COLLECT (STRING, "id", &id, |
||
1306 | OPTIONAL | STRING, "path", &path, |
||
1307 | OPTIONAL | STRING, "gettext-domain", &gettext_domain, |
||
1308 | OPTIONAL | STRING, "extends", &extends, |
||
1309 | OPTIONAL | STRING, "list-of", &list_of)) |
||
1310 | parse_state_start_schema (state, id, path, |
||
1311 | gettext_domain ? gettext_domain |
||
1312 | : state->schemalist_domain, |
||
1313 | extends, list_of, error); |
||
1314 | return; |
||
1315 | } |
||
1316 | |||
1317 | else if (strcmp (element_name, "enum") == 0) |
||
1318 | { |
||
1319 | const gchar *id; |
||
1320 | if (COLLECT (STRING, "id", &id)) |
||
1321 | parse_state_start_enum (state, id, FALSE, error); |
||
1322 | return; |
||
1323 | } |
||
1324 | |||
1325 | else if (strcmp (element_name, "flags") == 0) |
||
1326 | { |
||
1327 | const gchar *id; |
||
1328 | if (COLLECT (STRING, "id", &id)) |
||
1329 | parse_state_start_enum (state, id, TRUE, error); |
||
1330 | return; |
||
1331 | } |
||
1332 | } |
||
1333 | |||
1334 | |||
1335 | /* children of <schema> {{{3 */ |
||
1336 | else if (strcmp (container, "schema") == 0) |
||
1337 | { |
||
1338 | if (strcmp (element_name, "key") == 0) |
||
1339 | { |
||
1340 | const gchar *name, *type_string, *enum_type, *flags_type; |
||
1341 | |||
1342 | if (COLLECT (STRING, "name", &name, |
||
1343 | OPTIONAL | STRING, "type", &type_string, |
||
1344 | OPTIONAL | STRING, "enum", &enum_type, |
||
1345 | OPTIONAL | STRING, "flags", &flags_type)) |
||
1346 | |||
1347 | state->key_state = schema_state_add_key (state->schema_state, |
||
1348 | state->enum_table, |
||
1349 | state->flags_table, |
||
1350 | name, type_string, |
||
1351 | enum_type, flags_type, |
||
1352 | error); |
||
1353 | return; |
||
1354 | } |
||
1355 | else if (strcmp (element_name, "child") == 0) |
||
1356 | { |
||
1357 | const gchar *name, *schema; |
||
1358 | |||
1359 | if (COLLECT (STRING, "name", &name, STRING, "schema", &schema)) |
||
1360 | schema_state_add_child (state->schema_state, |
||
1361 | name, schema, error); |
||
1362 | return; |
||
1363 | } |
||
1364 | else if (strcmp (element_name, "override") == 0) |
||
1365 | { |
||
1366 | const gchar *name, *l10n, *context; |
||
1367 | |||
1368 | if (COLLECT (STRING, "name", &name, |
||
1369 | OPTIONAL | STRING, "l10n", &l10n, |
||
1370 | OPTIONAL | STRING, "context", &context)) |
||
1371 | schema_state_add_override (state->schema_state, |
||
1372 | &state->key_state, &state->string, |
||
1373 | name, l10n, context, error); |
||
1374 | return; |
||
1375 | } |
||
1376 | } |
||
1377 | |||
1378 | /* children of <key> {{{3 */ |
||
1379 | else if (strcmp (container, "key") == 0) |
||
1380 | { |
||
1381 | if (strcmp (element_name, "default") == 0) |
||
1382 | { |
||
1383 | const gchar *l10n, *context; |
||
1384 | if (COLLECT (STRING | OPTIONAL, "l10n", &l10n, |
||
1385 | STRING | OPTIONAL, "context", &context)) |
||
1386 | state->string = key_state_start_default (state->key_state, |
||
1387 | l10n, context, error); |
||
1388 | return; |
||
1389 | } |
||
1390 | |||
1391 | else if (strcmp (element_name, "summary") == 0) |
||
1392 | { |
||
1393 | if (NO_ATTRS ()) |
||
1394 | { |
||
1395 | if (state->key_state->summary_seen && state->strict) |
||
1396 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
1397 | _("Only one <%s> element allowed inside <%s>"), |
||
1398 | element_name, container); |
||
1399 | else |
||
1400 | state->string = g_string_new (NULL); |
||
1401 | |||
1402 | state->key_state->summary_seen = TRUE; |
||
1403 | } |
||
1404 | return; |
||
1405 | } |
||
1406 | |||
1407 | else if (strcmp (element_name, "description") == 0) |
||
1408 | { |
||
1409 | if (NO_ATTRS ()) |
||
1410 | { |
||
1411 | if (state->key_state->description_seen && state->strict) |
||
1412 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
1413 | _("Only one <%s> element allowed inside <%s>"), |
||
1414 | element_name, container); |
||
1415 | else |
||
1416 | state->string = g_string_new (NULL); |
||
1417 | |||
1418 | state->key_state->description_seen = TRUE; |
||
1419 | } |
||
1420 | return; |
||
1421 | } |
||
1422 | |||
1423 | else if (strcmp (element_name, "range") == 0) |
||
1424 | { |
||
1425 | const gchar *min, *max; |
||
1426 | if (COLLECT (STRING | OPTIONAL, "min", &min, |
||
1427 | STRING | OPTIONAL, "max", &max)) |
||
1428 | key_state_set_range (state->key_state, min, max, error); |
||
1429 | return; |
||
1430 | } |
||
1431 | |||
1432 | else if (strcmp (element_name, "choices") == 0) |
||
1433 | { |
||
1434 | if (NO_ATTRS ()) |
||
1435 | key_state_start_choices (state->key_state, error); |
||
1436 | return; |
||
1437 | } |
||
1438 | |||
1439 | else if (strcmp (element_name, "aliases") == 0) |
||
1440 | { |
||
1441 | if (NO_ATTRS ()) |
||
1442 | key_state_start_aliases (state->key_state, error); |
||
1443 | return; |
||
1444 | } |
||
1445 | } |
||
1446 | |||
1447 | |||
1448 | /* children of <choices> {{{3 */ |
||
1449 | else if (strcmp (container, "choices") == 0) |
||
1450 | { |
||
1451 | if (strcmp (element_name, "choice") == 0) |
||
1452 | { |
||
1453 | const gchar *value; |
||
1454 | if (COLLECT (STRING, "value", &value)) |
||
1455 | key_state_add_choice (state->key_state, value, error); |
||
1456 | return; |
||
1457 | } |
||
1458 | } |
||
1459 | |||
1460 | |||
1461 | /* children of <aliases> {{{3 */ |
||
1462 | else if (strcmp (container, "aliases") == 0) |
||
1463 | { |
||
1464 | if (strcmp (element_name, "alias") == 0) |
||
1465 | { |
||
1466 | const gchar *value, *target; |
||
1467 | if (COLLECT (STRING, "value", &value, STRING, "target", &target)) |
||
1468 | key_state_add_alias (state->key_state, value, target, error); |
||
1469 | return; |
||
1470 | } |
||
1471 | } |
||
1472 | |||
1473 | |||
1474 | /* children of <enum> {{{3 */ |
||
1475 | else if (strcmp (container, "enum") == 0 || |
||
1476 | strcmp (container, "flags") == 0) |
||
1477 | { |
||
1478 | if (strcmp (element_name, "value") == 0) |
||
1479 | { |
||
1480 | const gchar *nick, *valuestr; |
||
1481 | if (COLLECT (STRING, "nick", &nick, |
||
1482 | STRING, "value", &valuestr)) |
||
1483 | enum_state_add_value (state->enum_state, nick, valuestr, error); |
||
1484 | return; |
||
1485 | } |
||
1486 | } |
||
1487 | /* 3}}} */ |
||
1488 | |||
1489 | if (container) |
||
1490 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, |
||
1491 | _("Element <%s> not allowed inside <%s>"), |
||
1492 | element_name, container); |
||
1493 | else |
||
1494 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, |
||
1495 | _("Element <%s> not allowed at the top level"), element_name); |
||
1496 | } |
||
1497 | /* 2}}} */ |
||
1498 | /* End element {{{2 */ |
||
1499 | |||
1500 | static void |
||
1501 | key_state_end (KeyState **state_ptr, |
||
1502 | GError **error) |
||
1503 | { |
||
1504 | KeyState *state; |
||
1505 | |||
1506 | state = *state_ptr; |
||
1507 | *state_ptr = NULL; |
||
1508 | |||
1509 | if (state->default_value == NULL) |
||
1510 | { |
||
1511 | g_set_error_literal (error, |
||
1512 | G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
1513 | "element <default> is required in <key>"); |
||
1514 | return; |
||
1515 | } |
||
1516 | } |
||
1517 | |||
1518 | static void |
||
1519 | schema_state_end (SchemaState **state_ptr, |
||
1520 | GError **error) |
||
1521 | { |
||
1522 | *state_ptr = NULL; |
||
1523 | } |
||
1524 | |||
1525 | static void |
||
1526 | end_element (GMarkupParseContext *context, |
||
1527 | const gchar *element_name, |
||
1528 | gpointer user_data, |
||
1529 | GError **error) |
||
1530 | { |
||
1531 | ParseState *state = user_data; |
||
1532 | |||
1533 | if (strcmp (element_name, "schemalist") == 0) |
||
1534 | { |
||
1535 | g_free (state->schemalist_domain); |
||
1536 | state->schemalist_domain = NULL; |
||
1537 | } |
||
1538 | |||
1539 | else if (strcmp (element_name, "enum") == 0 || |
||
1540 | strcmp (element_name, "flags") == 0) |
||
1541 | enum_state_end (&state->enum_state, error); |
||
1542 | |||
1543 | else if (strcmp (element_name, "schema") == 0) |
||
1544 | schema_state_end (&state->schema_state, error); |
||
1545 | |||
1546 | else if (strcmp (element_name, "override") == 0) |
||
1547 | override_state_end (&state->key_state, &state->string, error); |
||
1548 | |||
1549 | else if (strcmp (element_name, "key") == 0) |
||
1550 | key_state_end (&state->key_state, error); |
||
1551 | |||
1552 | else if (strcmp (element_name, "default") == 0) |
||
1553 | key_state_end_default (state->key_state, &state->string, error); |
||
1554 | |||
1555 | else if (strcmp (element_name, "choices") == 0) |
||
1556 | key_state_end_choices (state->key_state, error); |
||
1557 | |||
1558 | else if (strcmp (element_name, "aliases") == 0) |
||
1559 | key_state_end_aliases (state->key_state, error); |
||
1560 | |||
1561 | if (state->string) |
||
1562 | { |
||
1563 | g_string_free (state->string, TRUE); |
||
1564 | state->string = NULL; |
||
1565 | } |
||
1566 | } |
||
1567 | /* Text {{{2 */ |
||
1568 | static void |
||
1569 | text (GMarkupParseContext *context, |
||
1570 | const gchar *text, |
||
1571 | gsize text_len, |
||
1572 | gpointer user_data, |
||
1573 | GError **error) |
||
1574 | { |
||
1575 | ParseState *state = user_data; |
||
1576 | |||
1577 | if (state->string) |
||
1578 | { |
||
1579 | /* we are expecting a string, so store the text data. |
||
1580 | * |
||
1581 | * we store the data verbatim here and deal with whitespace |
||
1582 | * later on. there are two reasons for that: |
||
1583 | * |
||
1584 | * 1) whitespace is handled differently depending on the tag |
||
1585 | * type. |
||
1586 | * |
||
1587 | * 2) we could do leading whitespace removal by refusing to |
||
1588 | * insert it into state->string if it's at the start, but for |
||
1589 | * trailing whitespace, we have no idea if there is another |
||
1590 | * text() call coming or not. |
||
1591 | */ |
||
1592 | g_string_append_len (state->string, text, text_len); |
||
1593 | } |
||
1594 | else |
||
1595 | { |
||
1596 | /* string is not expected: accept (and ignore) pure whitespace */ |
||
1597 | gsize i; |
||
1598 | |||
1599 | for (i = 0; i < text_len; i++) |
||
1600 | if (!g_ascii_isspace (text[i])) |
||
1601 | { |
||
1602 | g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, |
||
1603 | _("text may not appear inside <%s>"), |
||
1604 | g_markup_parse_context_get_element (context)); |
||
1605 | break; |
||
1606 | } |
||
1607 | } |
||
1608 | } |
||
1609 | |||
1610 | /* Write to GVDB {{{1 */ |
||
1611 | typedef struct |
||
1612 | { |
||
1613 | GHashTable *table; |
||
1614 | GvdbItem *root; |
||
1615 | } GvdbPair; |
||
1616 | |||
1617 | static void |
||
1618 | gvdb_pair_init (GvdbPair *pair) |
||
1619 | { |
||
1620 | pair->table = gvdb_hash_table_new (NULL, NULL); |
||
1621 | pair->root = gvdb_hash_table_insert (pair->table, ""); |
||
1622 | } |
||
1623 | |||
1624 | typedef struct |
||
1625 | { |
||
1626 | GHashTable *schema_table; |
||
1627 | GvdbPair root_pair; |
||
1628 | } WriteToFileData; |
||
1629 | |||
1630 | typedef struct |
||
1631 | { |
||
1632 | GHashTable *schema_table; |
||
1633 | GvdbPair pair; |
||
1634 | gboolean l10n; |
||
1635 | } OutputSchemaData; |
||
1636 | |||
1637 | static void |
||
1638 | output_key (gpointer key, |
||
1639 | gpointer value, |
||
1640 | gpointer user_data) |
||
1641 | { |
||
1642 | OutputSchemaData *data; |
||
1643 | const gchar *name; |
||
1644 | KeyState *state; |
||
1645 | GvdbItem *item; |
||
1646 | |||
1647 | name = key; |
||
1648 | state = value; |
||
1649 | data = user_data; |
||
1650 | |||
1651 | item = gvdb_hash_table_insert (data->pair.table, name); |
||
1652 | gvdb_item_set_parent (item, data->pair.root); |
||
1653 | gvdb_item_set_value (item, key_state_serialise (state)); |
||
1654 | |||
1655 | if (state->l10n) |
||
1656 | data->l10n = TRUE; |
||
1657 | |||
1658 | if (state->child_schema && |
||
1659 | !g_hash_table_lookup (data->schema_table, state->child_schema)) |
||
1660 | g_printerr ("warning: undefined reference to <schema id='%s'/>\n", |
||
1661 | state->child_schema); |
||
1662 | } |
||
1663 | |||
1664 | static void |
||
1665 | output_schema (gpointer key, |
||
1666 | gpointer value, |
||
1667 | gpointer user_data) |
||
1668 | { |
||
1669 | WriteToFileData *wtf_data = user_data; |
||
1670 | OutputSchemaData data; |
||
1671 | GvdbPair *root_pair; |
||
1672 | SchemaState *state; |
||
1673 | const gchar *id; |
||
1674 | GvdbItem *item; |
||
1675 | |||
1676 | id = key; |
||
1677 | state = value; |
||
1678 | root_pair = &wtf_data->root_pair; |
||
1679 | |||
1680 | data.schema_table = wtf_data->schema_table; |
||
1681 | gvdb_pair_init (&data.pair); |
||
1682 | data.l10n = FALSE; |
||
1683 | |||
1684 | item = gvdb_hash_table_insert (root_pair->table, id); |
||
1685 | gvdb_item_set_parent (item, root_pair->root); |
||
1686 | gvdb_item_set_hash_table (item, data.pair.table); |
||
1687 | |||
1688 | g_hash_table_foreach (state->keys, output_key, &data); |
||
1689 | |||
1690 | if (state->path) |
||
1691 | gvdb_hash_table_insert_string (data.pair.table, ".path", state->path); |
||
1692 | |||
1693 | if (state->extends_name) |
||
1694 | gvdb_hash_table_insert_string (data.pair.table, ".extends", |
||
1695 | state->extends_name); |
||
1696 | |||
1697 | if (state->list_of) |
||
1698 | gvdb_hash_table_insert_string (data.pair.table, ".list-of", |
||
1699 | state->list_of); |
||
1700 | |||
1701 | if (data.l10n) |
||
1702 | gvdb_hash_table_insert_string (data.pair.table, |
||
1703 | ".gettext-domain", |
||
1704 | state->gettext_domain); |
||
1705 | } |
||
1706 | |||
1707 | static gboolean |
||
1708 | write_to_file (GHashTable *schema_table, |
||
1709 | const gchar *filename, |
||
1710 | GError **error) |
||
1711 | { |
||
1712 | WriteToFileData data; |
||
1713 | gboolean success; |
||
1714 | |||
1715 | data.schema_table = schema_table; |
||
1716 | |||
1717 | gvdb_pair_init (&data.root_pair); |
||
1718 | |||
1719 | g_hash_table_foreach (schema_table, output_schema, &data); |
||
1720 | |||
1721 | success = gvdb_table_write_contents (data.root_pair.table, filename, |
||
1722 | G_BYTE_ORDER != G_LITTLE_ENDIAN, |
||
1723 | error); |
||
1724 | g_hash_table_unref (data.root_pair.table); |
||
1725 | |||
1726 | return success; |
||
1727 | } |
||
1728 | |||
1729 | /* Parser driver {{{1 */ |
||
1730 | static GHashTable * |
||
1731 | parse_gschema_files (gchar **files, |
||
1732 | gboolean strict) |
||
1733 | { |
||
1734 | GMarkupParser parser = { start_element, end_element, text }; |
||
1735 | ParseState state = { 0, }; |
||
1736 | const gchar *filename; |
||
1737 | GError *error = NULL; |
||
1738 | |||
1739 | state.strict = strict; |
||
1740 | |||
1741 | state.enum_table = g_hash_table_new_full (g_str_hash, g_str_equal, |
||
1742 | g_free, enum_state_free); |
||
1743 | |||
1744 | state.flags_table = g_hash_table_new_full (g_str_hash, g_str_equal, |
||
1745 | g_free, enum_state_free); |
||
1746 | |||
1747 | state.schema_table = g_hash_table_new_full (g_str_hash, g_str_equal, |
||
1748 | g_free, schema_state_free); |
||
1749 | |||
1750 | while ((filename = *files++) != NULL) |
||
1751 | { |
||
1752 | GMarkupParseContext *context; |
||
1753 | gchar *contents; |
||
1754 | gsize size; |
||
1755 | gint line, col; |
||
1756 | |||
1757 | if (!g_file_get_contents (filename, &contents, &size, &error)) |
||
1758 | { |
||
1759 | fprintf (stderr, "%s\n", error->message); |
||
1760 | g_clear_error (&error); |
||
1761 | continue; |
||
1762 | } |
||
1763 | |||
1764 | context = g_markup_parse_context_new (&parser, |
||
1765 | G_MARKUP_TREAT_CDATA_AS_TEXT | |
||
1766 | G_MARKUP_PREFIX_ERROR_POSITION | |
||
1767 | G_MARKUP_IGNORE_QUALIFIED, |
||
1768 | &state, NULL); |
||
1769 | |||
1770 | |||
1771 | if (!g_markup_parse_context_parse (context, contents, size, &error) || |
||
1772 | !g_markup_parse_context_end_parse (context, &error)) |
||
1773 | { |
||
1774 | GSList *item; |
||
1775 | |||
1776 | /* back out any changes from this file */ |
||
1777 | for (item = state.this_file_schemas; item; item = item->next) |
||
1778 | g_hash_table_remove (state.schema_table, item->data); |
||
1779 | |||
1780 | for (item = state.this_file_flagss; item; item = item->next) |
||
1781 | g_hash_table_remove (state.flags_table, item->data); |
||
1782 | |||
1783 | for (item = state.this_file_enums; item; item = item->next) |
||
1784 | g_hash_table_remove (state.enum_table, item->data); |
||
1785 | |||
1786 | /* let them know */ |
||
1787 | g_markup_parse_context_get_position (context, &line, &col); |
||
1788 | fprintf (stderr, "%s:%d:%d %s. ", filename, line, col, error->message); |
||
1789 | g_clear_error (&error); |
||
1790 | |||
1791 | if (strict) |
||
1792 | { |
||
1793 | /* Translators: Do not translate "--strict". */ |
||
1794 | fprintf (stderr, _("--strict was specified; exiting.\n")); |
||
1795 | g_hash_table_unref (state.schema_table); |
||
1796 | g_hash_table_unref (state.flags_table); |
||
1797 | g_hash_table_unref (state.enum_table); |
||
1798 | |||
1799 | return NULL; |
||
1800 | } |
||
1801 | else |
||
1802 | fprintf (stderr, _("This entire file has been ignored.\n")); |
||
1803 | } |
||
1804 | |||
1805 | /* cleanup */ |
||
1806 | g_markup_parse_context_free (context); |
||
1807 | g_slist_free (state.this_file_schemas); |
||
1808 | g_slist_free (state.this_file_flagss); |
||
1809 | g_slist_free (state.this_file_enums); |
||
1810 | state.this_file_schemas = NULL; |
||
1811 | state.this_file_flagss = NULL; |
||
1812 | state.this_file_enums = NULL; |
||
1813 | } |
||
1814 | |||
1815 | g_hash_table_unref (state.flags_table); |
||
1816 | g_hash_table_unref (state.enum_table); |
||
1817 | |||
1818 | return state.schema_table; |
||
1819 | } |
||
1820 | |||
1821 | static gint |
||
1822 | compare_strings (gconstpointer a, |
||
1823 | gconstpointer b) |
||
1824 | { |
||
1825 | gchar *one = *(gchar **) a; |
||
1826 | gchar *two = *(gchar **) b; |
||
1827 | gint cmp; |
||
1828 | |||
1829 | cmp = g_str_has_suffix (two, ".enums.xml") - |
||
1830 | g_str_has_suffix (one, ".enums.xml"); |
||
1831 | |||
1832 | if (!cmp) |
||
1833 | cmp = strcmp (one, two); |
||
1834 | |||
1835 | return cmp; |
||
1836 | } |
||
1837 | |||
1838 | static gboolean |
||
1839 | set_overrides (GHashTable *schema_table, |
||
1840 | gchar **files, |
||
1841 | gboolean strict) |
||
1842 | { |
||
1843 | const gchar *filename; |
||
1844 | GError *error = NULL; |
||
1845 | |||
1846 | while ((filename = *files++)) |
||
1847 | { |
||
1848 | GKeyFile *key_file; |
||
1849 | gchar **groups; |
||
1850 | gint i; |
||
1851 | |||
1852 | key_file = g_key_file_new (); |
||
1853 | if (!g_key_file_load_from_file (key_file, filename, 0, &error)) |
||
1854 | { |
||
1855 | fprintf (stderr, "%s: %s. ", filename, error->message); |
||
1856 | g_key_file_free (key_file); |
||
1857 | g_clear_error (&error); |
||
1858 | |||
1859 | if (!strict) |
||
1860 | { |
||
1861 | fprintf (stderr, _("Ignoring this file.\n")); |
||
1862 | continue; |
||
1863 | } |
||
1864 | |||
1865 | fprintf (stderr, _("--strict was specified; exiting.\n")); |
||
1866 | return FALSE; |
||
1867 | } |
||
1868 | |||
1869 | groups = g_key_file_get_groups (key_file, NULL); |
||
1870 | |||
1871 | for (i = 0; groups[i]; i++) |
||
1872 | { |
||
1873 | const gchar *group = groups[i]; |
||
1874 | SchemaState *schema; |
||
1875 | gchar **keys; |
||
1876 | gint j; |
||
1877 | |||
1878 | schema = g_hash_table_lookup (schema_table, group); |
||
1879 | |||
1880 | if (schema == NULL) |
||
1881 | /* Having the schema not be installed is expected to be a |
||
1882 | * common case. Don't even emit an error message about |
||
1883 | * that. |
||
1884 | */ |
||
1885 | continue; |
||
1886 | |||
1887 | keys = g_key_file_get_keys (key_file, group, NULL, NULL); |
||
1888 | g_assert (keys != NULL); |
||
1889 | |||
1890 | for (j = 0; keys[j]; j++) |
||
1891 | { |
||
1892 | const gchar *key = keys[j]; |
||
1893 | KeyState *state; |
||
1894 | GVariant *value; |
||
1895 | gchar *string; |
||
1896 | |||
1897 | state = g_hash_table_lookup (schema->keys, key); |
||
1898 | |||
1899 | if (state == NULL) |
||
1900 | { |
||
1901 | fprintf (stderr, _("No such key '%s' in schema '%s' as " |
||
1902 | "specified in override file '%s'"), |
||
1903 | key, group, filename); |
||
1904 | |||
1905 | if (!strict) |
||
1906 | { |
||
1907 | fprintf (stderr, _("; ignoring override for this key.\n")); |
||
1908 | continue; |
||
1909 | } |
||
1910 | |||
1911 | fprintf (stderr, _(" and --strict was specified; exiting.\n")); |
||
1912 | g_key_file_free (key_file); |
||
1913 | g_strfreev (groups); |
||
1914 | g_strfreev (keys); |
||
1915 | |||
1916 | return FALSE; |
||
1917 | } |
||
1918 | |||
1919 | string = g_key_file_get_value (key_file, group, key, NULL); |
||
1920 | g_assert (string != NULL); |
||
1921 | |||
1922 | value = g_variant_parse (state->type, string, |
||
1923 | NULL, NULL, &error); |
||
1924 | |||
1925 | if (value == NULL) |
||
1926 | { |
||
1927 | fprintf (stderr, _("error parsing key '%s' in schema '%s' " |
||
1928 | "as specified in override file '%s': " |
||
1929 | "%s."), |
||
1930 | key, group, filename, error->message); |
||
1931 | |||
1932 | g_clear_error (&error); |
||
1933 | g_free (string); |
||
1934 | |||
1935 | if (!strict) |
||
1936 | { |
||
1937 | fprintf (stderr, _("Ignoring override for this key.\n")); |
||
1938 | continue; |
||
1939 | } |
||
1940 | |||
1941 | fprintf (stderr, _("--strict was specified; exiting.\n")); |
||
1942 | g_key_file_free (key_file); |
||
1943 | g_strfreev (groups); |
||
1944 | g_strfreev (keys); |
||
1945 | |||
1946 | return FALSE; |
||
1947 | } |
||
1948 | |||
1949 | if (state->minimum) |
||
1950 | { |
||
1951 | if (g_variant_compare (value, state->minimum) < 0 || |
||
1952 | g_variant_compare (value, state->maximum) > 0) |
||
1953 | { |
||
1954 | fprintf (stderr, |
||
1955 | _("override for key '%s' in schema '%s' in " |
||
1956 | "override file '%s' is outside the range " |
||
1957 | "given in the schema"), |
||
1958 | key, group, filename); |
||
1959 | |||
1960 | g_variant_unref (value); |
||
1961 | g_free (string); |
||
1962 | |||
1963 | if (!strict) |
||
1964 | { |
||
1965 | fprintf (stderr, _("; ignoring override for this key.\n")); |
||
1966 | continue; |
||
1967 | } |
||
1968 | |||
1969 | fprintf (stderr, _(" and --strict was specified; exiting.\n")); |
||
1970 | g_key_file_free (key_file); |
||
1971 | g_strfreev (groups); |
||
1972 | g_strfreev (keys); |
||
1973 | |||
1974 | return FALSE; |
||
1975 | } |
||
1976 | } |
||
1977 | |||
1978 | else if (state->strinfo->len) |
||
1979 | { |
||
1980 | if (!is_valid_choices (value, state->strinfo)) |
||
1981 | { |
||
1982 | fprintf (stderr, |
||
1983 | _("override for key '%s' in schema '%s' in " |
||
1984 | "override file '%s' is not in the list " |
||
1985 | "of valid choices"), |
||
1986 | key, group, filename); |
||
1987 | |||
1988 | g_variant_unref (value); |
||
1989 | g_free (string); |
||
1990 | |||
1991 | if (!strict) |
||
1992 | { |
||
1993 | fprintf (stderr, _("; ignoring override for this key.\n")); |
||
1994 | continue; |
||
1995 | } |
||
1996 | |||
1997 | fprintf (stderr, _(" and --strict was specified; exiting.\n")); |
||
1998 | g_key_file_free (key_file); |
||
1999 | g_strfreev (groups); |
||
2000 | g_strfreev (keys); |
||
2001 | |||
2002 | return FALSE; |
||
2003 | } |
||
2004 | } |
||
2005 | |||
2006 | g_variant_unref (state->default_value); |
||
2007 | state->default_value = value; |
||
2008 | g_free (string); |
||
2009 | } |
||
2010 | |||
2011 | g_strfreev (keys); |
||
2012 | } |
||
2013 | |||
2014 | g_strfreev (groups); |
||
2015 | } |
||
2016 | |||
2017 | return TRUE; |
||
2018 | } |
||
2019 | |||
2020 | int |
||
2021 | main (int argc, char **argv) |
||
2022 | { |
||
2023 | GError *error; |
||
2024 | GHashTable *table; |
||
2025 | GDir *dir; |
||
2026 | const gchar *file; |
||
2027 | gchar *srcdir; |
||
2028 | gchar *targetdir = NULL; |
||
2029 | gchar *target; |
||
2030 | gboolean dry_run = FALSE; |
||
2031 | gboolean strict = FALSE; |
||
2032 | gchar **schema_files = NULL; |
||
2033 | gchar **override_files = NULL; |
||
2034 | GOptionContext *context; |
||
2035 | GOptionEntry entries[] = { |
||
2036 | { "targetdir", 0, 0, G_OPTION_ARG_FILENAME, &targetdir, N_("where to store the gschemas.compiled file"), N_("DIRECTORY") }, |
||
2037 | { "strict", 0, 0, G_OPTION_ARG_NONE, &strict, N_("Abort on any errors in schemas"), NULL }, |
||
2038 | { "dry-run", 0, 0, G_OPTION_ARG_NONE, &dry_run, N_("Do not write the gschema.compiled file"), NULL }, |
||
2039 | { "allow-any-name", 0, 0, G_OPTION_ARG_NONE, &allow_any_name, N_("Do not enforce key name restrictions") }, |
||
2040 | |||
2041 | /* These options are only for use in the gschema-compile tests */ |
||
2042 | { "schema-file", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME_ARRAY, &schema_files, NULL, NULL }, |
||
2043 | { NULL } |
||
2044 | }; |
||
2045 | |||
2046 | #ifdef G_OS_WIN32 |
||
2047 | gchar *tmp; |
||
2048 | #endif |
||
2049 | |||
2050 | setlocale (LC_ALL, ""); |
||
2051 | textdomain (GETTEXT_PACKAGE); |
||
2052 | |||
2053 | #ifdef G_OS_WIN32 |
||
2054 | tmp = _glib_get_locale_dir (); |
||
2055 | bindtextdomain (GETTEXT_PACKAGE, tmp); |
||
2056 | g_free (tmp); |
||
2057 | #else |
||
2058 | bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR); |
||
2059 | #endif |
||
2060 | |||
2061 | #ifdef HAVE_BIND_TEXTDOMAIN_CODESET |
||
2062 | bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); |
||
2063 | #endif |
||
2064 | |||
2065 | context = g_option_context_new (N_("DIRECTORY")); |
||
2066 | g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); |
||
2067 | g_option_context_set_summary (context, |
||
2068 | N_("Compile all GSettings schema files into a schema cache.\n" |
||
2069 | "Schema files are required to have the extension .gschema.xml,\n" |
||
2070 | "and the cache file is called gschemas.compiled.")); |
||
2071 | g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); |
||
2072 | |||
2073 | error = NULL; |
||
2074 | if (!g_option_context_parse (context, &argc, &argv, &error)) |
||
2075 | { |
||
2076 | fprintf (stderr, "%s\n", error->message); |
||
2077 | return 1; |
||
2078 | } |
||
2079 | |||
2080 | g_option_context_free (context); |
||
2081 | |||
2082 | if (!schema_files && argc != 2) |
||
2083 | { |
||
2084 | fprintf (stderr, _("You should give exactly one directory name\n")); |
||
2085 | return 1; |
||
2086 | } |
||
2087 | |||
2088 | srcdir = argv[1]; |
||
2089 | |||
2090 | if (targetdir == NULL) |
||
2091 | targetdir = srcdir; |
||
2092 | |||
2093 | target = g_build_filename (targetdir, "gschemas.compiled", NULL); |
||
2094 | |||
2095 | if (!schema_files) |
||
2096 | { |
||
2097 | GPtrArray *overrides; |
||
2098 | GPtrArray *files; |
||
2099 | |||
2100 | files = g_ptr_array_new (); |
||
2101 | overrides = g_ptr_array_new (); |
||
2102 | |||
2103 | dir = g_dir_open (srcdir, 0, &error); |
||
2104 | if (dir == NULL) |
||
2105 | { |
||
2106 | fprintf (stderr, "%s\n", error->message); |
||
2107 | return 1; |
||
2108 | } |
||
2109 | |||
2110 | while ((file = g_dir_read_name (dir)) != NULL) |
||
2111 | { |
||
2112 | if (g_str_has_suffix (file, ".gschema.xml") || |
||
2113 | g_str_has_suffix (file, ".enums.xml")) |
||
2114 | g_ptr_array_add (files, g_build_filename (srcdir, file, NULL)); |
||
2115 | |||
2116 | else if (g_str_has_suffix (file, ".gschema.override")) |
||
2117 | g_ptr_array_add (overrides, |
||
2118 | g_build_filename (srcdir, file, NULL)); |
||
2119 | } |
||
2120 | |||
2121 | if (files->len == 0) |
||
2122 | { |
||
2123 | fprintf (stdout, _("No schema files found: ")); |
||
2124 | |||
2125 | if (g_unlink (target)) |
||
2126 | fprintf (stdout, _("doing nothing.\n")); |
||
2127 | |||
2128 | else |
||
2129 | fprintf (stdout, _("removed existing output file.\n")); |
||
2130 | |||
2131 | return 0; |
||
2132 | } |
||
2133 | g_ptr_array_sort (files, compare_strings); |
||
2134 | g_ptr_array_add (files, NULL); |
||
2135 | |||
2136 | g_ptr_array_sort (overrides, compare_strings); |
||
2137 | g_ptr_array_add (overrides, NULL); |
||
2138 | |||
2139 | schema_files = (char **) g_ptr_array_free (files, FALSE); |
||
2140 | override_files = (gchar **) g_ptr_array_free (overrides, FALSE); |
||
2141 | } |
||
2142 | |||
2143 | if ((table = parse_gschema_files (schema_files, strict)) == NULL) |
||
2144 | { |
||
2145 | g_free (target); |
||
2146 | return 1; |
||
2147 | } |
||
2148 | |||
2149 | if (override_files != NULL && |
||
2150 | !set_overrides (table, override_files, strict)) |
||
2151 | { |
||
2152 | g_free (target); |
||
2153 | return 1; |
||
2154 | } |
||
2155 | |||
2156 | if (!dry_run && !write_to_file (table, target, &error)) |
||
2157 | { |
||
2158 | fprintf (stderr, "%s\n", error->message); |
||
2159 | g_free (target); |
||
2160 | return 1; |
||
2161 | } |
||
2162 | |||
2163 | g_free (target); |
||
2164 | |||
2165 | return 0; |
||
2166 | } |
||
2167 | |||
2168 | /* Epilogue {{{1 */ |
||
2169 | |||
2170 | /* vim:set foldmethod=marker: */ |