2a98b4f4a74f1eda094bbad2adefbfa11f0d84e9
[platform/upstream/glib.git] / gio / gschema-compile.c
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, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Author: Ryan Lortie <desrt@desrt.ca>
20  */
21
22 #include "config.h"
23
24 #include <locale.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <stdio.h>
28
29 #include <gi18n.h>
30
31 #include "gvdb/gvdb-builder.h"
32
33 typedef struct
34 {
35   gboolean byteswap;
36
37   GVariantBuilder key_options;
38   GHashTable *schemas;
39   gchar *schemalist_domain;
40
41   GHashTable *schema;
42   GvdbItem *schema_root;
43   gchar *schema_domain;
44
45   GString *string;
46
47   GvdbItem *key;
48   GVariant *value;
49   GVariant *min, *max;
50   GVariant *strings;
51   gchar l10n;
52   gchar *context;
53   GVariantType *type;
54 } ParseState;
55
56 static gboolean allow_any_name = FALSE;
57
58 static gboolean
59 is_valid_keyname (const gchar  *key,
60                   GError      **error)
61 {
62   gint i;
63
64   if (key[0] == '\0')
65     {
66       g_set_error_literal (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
67                            "empty names are not permitted");
68       return FALSE;
69     }
70
71   if (allow_any_name)
72     return TRUE;
73
74   if (!g_ascii_islower (key[0]))
75     {
76       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
77                    "invalid name '%s': names must begin "
78                    "with a lowercase letter", key);
79       return FALSE;
80     }
81
82   for (i = 1; key[i]; i++)
83     {
84       if (key[i] != '-' &&
85           !g_ascii_islower (key[i]) &&
86           !g_ascii_isdigit (key[i]))
87         {
88           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
89                        "invalid name '%s': invalid character '%c'; "
90                        "only lowercase letters, numbers and dash ('-') "
91                        "are permitted.", key, key[i]);
92           return FALSE;
93         }
94
95       if (key[i] == '-' && key[i + 1] == '-')
96         {
97           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
98                        "invalid name '%s': two successive dashes ('--') are "
99                        "not permitted.", key);
100           return FALSE;
101         }
102     }
103
104   if (key[i - 1] == '-')
105     {
106       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
107                    "invalid name '%s': the last character may not be a "
108                    "dash ('-').", key);
109       return FALSE;
110     }
111
112   if (i > 32)
113     {
114       g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
115                    "invalid name '%s': maximum length is 32", key);
116       return FALSE;
117     }
118
119   return TRUE;
120 }
121
122 static void
123 start_element (GMarkupParseContext  *context,
124                const gchar          *element_name,
125                const gchar         **attribute_names,
126                const gchar         **attribute_values,
127                gpointer              user_data,
128                GError              **error)
129 {
130   ParseState *state = user_data;
131   const GSList *element_stack;
132   const gchar *container;
133
134   element_stack = g_markup_parse_context_get_element_stack (context);
135   container = element_stack->next ? element_stack->next->data : NULL;
136
137 #define COLLECT(first, ...) \
138   g_markup_collect_attributes (element_name,                                 \
139                                attribute_names, attribute_values, error,     \
140                                first, __VA_ARGS__, G_MARKUP_COLLECT_INVALID)
141 #define OPTIONAL   G_MARKUP_COLLECT_OPTIONAL
142 #define STRDUP     G_MARKUP_COLLECT_STRDUP
143 #define STRING     G_MARKUP_COLLECT_STRING
144 #define NO_ARGS()  COLLECT (G_MARKUP_COLLECT_INVALID, NULL)
145
146   if (container == NULL)
147     {
148       if (strcmp (element_name, "schemalist") == 0)
149         {
150           COLLECT (OPTIONAL | STRDUP,
151                    "gettext-domain",
152                    &state->schemalist_domain);
153           return;
154         }
155     }
156   else if (strcmp (container, "schemalist") == 0)
157     {
158       if (strcmp (element_name, "schema") == 0)
159         {
160           const gchar *id, *path;
161
162           if (COLLECT (STRING, "id", &id,
163                        OPTIONAL | STRING, "path", &path,
164                        OPTIONAL | STRDUP, "gettext-domain",
165                                           &state->schema_domain))
166             {
167               if (!g_hash_table_lookup (state->schemas, id))
168                 {
169                   state->schema = gvdb_hash_table_new (state->schemas, id);
170                   state->schema_root = gvdb_hash_table_insert (state->schema, "");
171
172                   if (path != NULL)
173                     gvdb_hash_table_insert_string (state->schema,
174                                                    ".path", path);
175                 }
176               else
177                 g_set_error (error, G_MARKUP_ERROR,
178                              G_MARKUP_ERROR_INVALID_CONTENT,
179                              "<schema id='%s'> already specified", id);
180             }
181           return;
182         }
183     }
184   else if (strcmp (container, "schema") == 0)
185     {
186       if (strcmp (element_name, "key") == 0)
187         {
188           const gchar *name, *type;
189
190           if (COLLECT (STRING, "name", &name, STRING, "type", &type))
191             {
192               if (!is_valid_keyname (name, error))
193                 return;
194
195               if (!g_hash_table_lookup (state->schema, name))
196                 {
197                   state->key = gvdb_hash_table_insert (state->schema, name);
198                   gvdb_item_set_parent (state->key, state->schema_root);
199                 }
200               else
201                 {
202                   g_set_error (error, G_MARKUP_ERROR,
203                                G_MARKUP_ERROR_INVALID_CONTENT,
204                                "<key name='%s'> already specified", name);
205                   return;
206                 }
207
208               if (g_variant_type_string_is_valid (type))
209                 state->type = g_variant_type_new (type);
210               else
211                 {
212                   g_set_error (error, G_MARKUP_ERROR,
213                                G_MARKUP_ERROR_INVALID_CONTENT,
214                                "invalid GVariant type string '%s'", type);
215                   return;
216                 }
217
218               g_variant_builder_init (&state->key_options,
219                                       G_VARIANT_TYPE ("a{sv}"));
220             }
221
222           return;
223         }
224       else if (strcmp (element_name, "child") == 0)
225         {
226           const gchar *name, *schema;
227
228           if (COLLECT (STRING, "name", &name, STRING, "schema", &schema))
229             {
230               gchar *childname;
231
232               if (!is_valid_keyname (name, error))
233                 return;
234
235               childname = g_strconcat (name, "/", NULL);
236
237               if (!g_hash_table_lookup (state->schema, childname))
238                 gvdb_hash_table_insert_string (state->schema, childname, schema);
239               else
240                 g_set_error (error, G_MARKUP_ERROR,
241                              G_MARKUP_ERROR_INVALID_CONTENT,
242                              "<child name='%s'> already specified", name);
243
244               g_free (childname);
245               return;
246             }
247         }
248     }
249   else if (strcmp (container, "key") == 0)
250     {
251       if (strcmp (element_name, "default") == 0)
252         {
253           const gchar *l10n;
254
255           if (COLLECT (STRING | OPTIONAL, "l10n", &l10n,
256                        STRDUP | OPTIONAL, "context", &state->context))
257             {
258               if (l10n != NULL)
259                 {
260                   if (!g_hash_table_lookup (state->schema, ".gettext-domain"))
261                     {
262                       const gchar *domain = state->schema_domain ?
263                                             state->schema_domain :
264                                             state->schemalist_domain;
265
266                       if (domain == NULL)
267                         {
268                           g_set_error_literal (error, G_MARKUP_ERROR,
269                                                G_MARKUP_ERROR_INVALID_CONTENT,
270                                                "l10n requested, but no "
271                                                "gettext domain given");
272                           return;
273                         }
274
275                       gvdb_hash_table_insert_string (state->schema,
276                                                      ".gettext-domain",
277                                                      domain);
278
279                       if (strcmp (l10n, "messages") == 0)
280                         state->l10n = 'm';
281                       else if (strcmp (l10n, "time") == 0)
282                         state->l10n = 't';
283                       else
284                         {
285                           g_set_error (error, G_MARKUP_ERROR,
286                                        G_MARKUP_ERROR_INVALID_CONTENT,
287                                        "unsupported l10n category: %s", l10n);
288                           return;
289                         }
290                     }
291                 }
292               else
293                 {
294                   state->l10n = '\0';
295
296                   if (state->context != NULL)
297                     {
298                       g_set_error_literal (error, G_MARKUP_ERROR,
299                                            G_MARKUP_ERROR_INVALID_CONTENT,
300                                            "translation context given for "
301                                            " value without l10n enabled");
302                       return;
303                     }
304                 }
305
306               state->string = g_string_new (NULL);
307             }
308
309           return;
310         }
311       else if (strcmp (element_name, "summary") == 0 ||
312                strcmp (element_name, "description") == 0)
313         {
314           state->string = g_string_new (NULL);
315           NO_ARGS ();
316           return;
317         }
318       else if (strcmp (element_name, "range") == 0)
319         {
320           NO_ARGS ();
321           return;
322         }
323     }
324   else if (strcmp (container, "range") == 0)
325     {
326       if (strcmp (element_name, "choice") == 0)
327         {
328           gchar *value;
329
330           if (COLLECT (STRDUP, "value", &value))
331             {
332             }
333
334           return;
335         }
336       else if (strcmp (element_name, "min") == 0)
337         {
338           NO_ARGS ();
339           return;
340         }
341       else if (strcmp (element_name, "max") == 0)
342         {
343           NO_ARGS ();
344           return;
345         }
346     }
347   else if (strcmp (container, "choice") == 0)
348     {
349     }
350
351   if (container)
352     g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
353                  "Element <%s> not allowed inside <%s>\n",
354                  element_name, container);
355   else
356     g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
357                  "Element <%s> not allowed at toplevel\n", element_name);
358 }
359
360 static void
361 end_element (GMarkupParseContext  *context,
362              const gchar          *element_name,
363              gpointer              user_data,
364              GError              **error)
365 {
366   ParseState *state = user_data;
367
368   if (strcmp (element_name, "default") == 0)
369     {
370       state->value = g_variant_parse (state->type, state->string->str,
371                                       NULL, NULL, error);
372
373       if (state->value == NULL)
374         return;
375
376       if (state->l10n)
377         {
378           if (state->context)
379             {
380               gint len;
381
382               /* Contextified messages are supported by prepending the
383                * context, followed by '\004' to the start of the message
384                * string.  We do that here to save GSettings the work
385                * later on.
386                *
387                * Note: we are about to g_free() the context anyway...
388                */
389               len = strlen (state->context);
390               state->context[len] = '\004';
391               g_string_prepend_len (state->string, state->context, len + 1);
392             }
393
394           g_variant_builder_add (&state->key_options, "{sv}", "l10n",
395                                  g_variant_new ("(ys)",
396                                                 state->l10n,
397                                                 state->string->str));
398         }
399
400       g_string_free (state->string, TRUE);
401       g_free (state->context);
402     }
403
404   else if (strcmp (element_name, "key") == 0)
405     {
406       if (state->value == NULL)
407         {
408           g_set_error_literal (error,
409                                G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
410                                "element <default> is required in <key>\n");
411           return;
412         }
413
414       gvdb_item_set_value (state->key, state->value);
415       gvdb_item_set_options (state->key,
416                              g_variant_builder_end (&state->key_options));
417     }
418
419   else if (strcmp (element_name, "summary") == 0 ||
420            strcmp (element_name, "description") == 0)
421     g_string_free (state->string, TRUE);
422 }
423
424 static void
425 text (GMarkupParseContext  *context,
426       const gchar          *text,
427       gsize                 text_len,
428       gpointer              user_data,
429       GError              **error)
430 {
431   ParseState *state = user_data;
432   gsize i;
433
434   for (i = 0; i < text_len; i++)
435     if (!g_ascii_isspace (text[i]))
436       {
437         if (state->string)
438           g_string_append_len (state->string, text, text_len);
439
440         else
441           g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
442                        "text may not appear inside <%s>\n",
443                        g_markup_parse_context_get_element (context));
444
445         break;
446       }
447 }
448
449 static GHashTable *
450 parse_gschema_files (gchar    **files,
451                      gboolean   byteswap,
452                      GError   **error)
453 {
454   GMarkupParser parser = { start_element, end_element, text };
455   GMarkupParseContext *context;
456   ParseState state = { byteswap, };
457   const gchar *filename;
458
459   context = g_markup_parse_context_new (&parser,
460                                         G_MARKUP_PREFIX_ERROR_POSITION,
461                                         &state, NULL);
462   state.schemas = gvdb_hash_table_new (NULL, NULL);
463
464   while ((filename = *files++) != NULL)
465     {
466       gchar *contents;
467       gsize size;
468
469       if (!g_file_get_contents (filename, &contents, &size, error))
470         return FALSE;
471
472       if (!g_markup_parse_context_parse (context, contents, size, error))
473         {
474           g_prefix_error (error, "%s: ", filename);
475           return FALSE;
476         }
477
478       if (!g_markup_parse_context_end_parse (context, error))
479         {
480           g_prefix_error (error, "%s: ", filename);
481           return FALSE;
482         }
483     }
484
485   return state.schemas;
486 }
487
488 int
489 main (int argc, char **argv)
490 {
491   gboolean byteswap = G_BYTE_ORDER != G_LITTLE_ENDIAN;
492   GError *error;
493   GHashTable *table;
494   GDir *dir;
495   const gchar *file;
496   GPtrArray *files;
497   gchar *srcdir;
498   gchar *targetdir = NULL;
499   gchar *target;
500   gboolean dry_run = FALSE;
501   gchar *one_schema_file = NULL;
502   GOptionContext *context;
503   GOptionEntry entries[] = {
504     { "targetdir", 0, 0, G_OPTION_ARG_FILENAME, &targetdir, N_("where to store the gschemas.compiled file"), N_("DIRECTORY") },
505     { "dry-run", 0, 0, G_OPTION_ARG_NONE, &dry_run, N_("Do not write the gschema.compiled file"), NULL },
506     { "allow-any-name", 0, 0, G_OPTION_ARG_NONE, &allow_any_name, N_("Do not enforce key name restrictions") },
507
508     /* These options are only for use in the gschema-compile tests */
509     { "one-schema-file", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &one_schema_file, NULL, NULL },
510     { NULL }
511   };
512
513   setlocale (LC_ALL, "");
514
515   context = g_option_context_new (N_("DIRECTORY"));
516   g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
517   g_option_context_set_summary (context,
518     N_("Compile all GSettings schema files into a schema cache.\n"
519        "Schema files are required to have the extension .gschema.xml,\n"
520        "and the cache file is called gschemas.compiled."));
521   g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
522
523   error = NULL;
524   if (!g_option_context_parse (context, &argc, &argv, &error))
525     {
526       fprintf (stderr, "%s", error->message);
527       return 1;
528     }
529
530   g_option_context_free (context);
531
532   if (!one_schema_file && argc != 2)
533     {
534       fprintf (stderr, _("You should give exactly one directory name\n"));
535       return 1;
536     }
537
538   srcdir = argv[1];
539
540   if (targetdir == NULL)
541     targetdir = srcdir;
542
543   target = g_build_filename (targetdir, "gschemas.compiled", NULL);
544
545   files = g_ptr_array_new ();
546   if (one_schema_file)
547     {
548       g_ptr_array_add (files, one_schema_file);
549     }
550   else
551     {
552       dir = g_dir_open (srcdir, 0, &error);
553       if (dir == NULL)
554         {
555           fprintf (stderr, "%s\n", error->message);
556           return 1;
557         }
558
559       while ((file = g_dir_read_name (dir)) != NULL)
560         {
561           if (g_str_has_suffix (file, ".gschema.xml"))
562             g_ptr_array_add (files, g_build_filename (srcdir, file, NULL));
563         }
564
565       if (files->len == 0)
566         {
567           fprintf (stderr, _("No schema files found\n"));
568           return 1;
569         }
570     }
571
572   g_ptr_array_add (files, NULL);
573
574   if (!(table = parse_gschema_files ((gchar **) files->pdata, byteswap, &error)) ||
575       (!dry_run && !gvdb_table_write_contents (table, target, byteswap, &error)))
576     {
577       fprintf (stderr, "%s\n", error->message);
578       return 1;
579     }
580
581   g_free (target);
582
583   return 0;
584 }