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