* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Author: Ryan Lortie <desrt@desrt.ca>
*/
/* Prologue {{{1 */
-#define _GNU_SOURCE
#include "config.h"
#include <gstdio.h>
-#include <locale.h>
+#include <gi18n.h>
+
#include <string.h>
-#include <unistd.h>
#include <stdio.h>
-
-#include <gi18n.h>
+#include <locale.h>
#include "gvdb/gvdb-builder.h"
#include "strinfo.c"
+#ifdef G_OS_WIN32
+#include "glib/glib-private.h"
+#endif
+
+static void
+strip_string (GString *string)
+{
+ gint i;
+
+ for (i = 0; g_ascii_isspace (string->str[i]); i++);
+ g_string_erase (string, 0, i);
+
+ if (string->len > 0)
+ {
+ /* len > 0, so there must be at least one non-whitespace character */
+ for (i = string->len - 1; g_ascii_isspace (string->str[i]); i--);
+ g_string_truncate (string, i + 1);
+ }
+}
+
/* Handling of <enum> {{{1 */
typedef struct
{
g_slice_free (EnumState, state);
}
-EnumState *
+static EnumState *
enum_state_new (gboolean is_flags)
{
EnumState *state;
return;
}
+ /* Silently drop the null case if it is mentioned.
+ * It is properly denoted with an empty array.
+ */
+ if (state->is_flags && value == 0)
+ return;
+
if (state->is_flags && (value & (value - 1)))
{
g_set_error (error, G_MARKUP_ERROR,
* If we loosen the one-bit-set restriction we need an overlap check.
*/
-
strinfo_builder_append_item (state->strinfo, nick, value);
}
*state_ptr = NULL;
if (state->strinfo->len == 0)
- g_set_error_literal (error,
- G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- "<enum> must contain at least one <value>");
+ g_set_error (error,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+ "<%s> must contain at least one <value>",
+ state->is_flags ? "flags" : "enum");
}
/* Handling of <key> {{{1 */
const gchar *max_str,
GError **error)
{
+ const struct {
+ const gchar type;
+ const gchar *min;
+ const gchar *max;
+ } table[] = {
+ { 'y', "0", "255" },
+ { 'n', "-32768", "32767" },
+ { 'q', "0", "65535" },
+ { 'i', "-2147483648", "2147483647" },
+ { 'u', "0", "4294967295" },
+ { 'x', "-9223372036854775808", "9223372036854775807" },
+ { 't', "0", "18446744073709551615" },
+ { 'd', "-inf", "inf" },
+ };
+ gboolean type_ok = FALSE;
+ gint i;
+
if (state->minimum)
{
g_set_error_literal (error, G_MARKUP_ERROR,
return;
}
- if (strchr ("ynqiuxtd", *(char *) state->type) == NULL)
+ for (i = 0; i < G_N_ELEMENTS (table); i++)
+ if (*(char *) state->type == table[i].type)
+ {
+ min_str = min_str ? min_str : table[i].min;
+ max_str = max_str ? max_str : table[i].max;
+ type_ok = TRUE;
+ break;
+ }
+
+ if (!type_ok)
{
gchar *type = g_variant_type_dup_string (state->type);
g_set_error (error, G_MARKUP_ERROR,
{
g_set_error_literal (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT,
- "<choices> can not be specified for keys "
+ "<choices> cannot be specified for keys "
"tagged as having an enumerated type");
return;
}
g_set_error_literal (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT,
"<aliases> already specified for this key");
-
- if (!state->is_flags && !state->is_enum && !state->has_choices)
+ else if (!state->is_flags && !state->is_enum && !state->has_choices)
g_set_error_literal (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT,
"<aliases> can only be specified for keys with "
/* translation */
if (state->l10n)
{
+ /* We are going to store the untranslated default for
+ * runtime translation according to the current locale.
+ * We need to strip leading and trailing whitespace from
+ * the string so that it's exactly the same as the one
+ * that ended up in the .po file for translation.
+ *
+ * We want to do this so that
+ *
+ * <default l10n='messages'>
+ * ['a', 'b', 'c']
+ * </default>
+ *
+ * ends up in the .po file like "['a', 'b', 'c']",
+ * omitting the extra whitespace at the start and end.
+ */
+ strip_string (state->unparsed_default_value);
+
if (state->l10n_context)
{
gint len;
if (state->strinfo->len)
{
GVariant *array;
+ guint32 *words;
gpointer data;
gsize size;
+ gint i;
data = state->strinfo->str;
size = state->strinfo->len;
+ words = data;
+ for (i = 0; i < size / sizeof (guint32); i++)
+ words[i] = GUINT32_TO_LE (words[i]);
+
array = g_variant_new_from_data (G_VARIANT_TYPE ("au"),
data, size, TRUE,
g_free, data);
{
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
_("invalid name '%s': invalid character '%c'; "
- "only lowercase letters, numbers and dash ('-') "
+ "only lowercase letters, numbers and hyphen ('-') "
"are permitted."), key, key[i]);
return FALSE;
}
if (key[i] == '-' && key[i + 1] == '-')
{
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- _("invalid name '%s': two successive dashes ('--') "
+ _("invalid name '%s': two successive hyphens ('--') "
"are not permitted."), key);
return FALSE;
}
{
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
_("invalid name '%s': the last character may not be a "
- "dash ('-')."), key);
+ "hyphen ('-')."), key);
return FALSE;
}
- if (i > 32)
+ if (i > 1024)
{
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- _("invalid name '%s': maximum length is 32"), key);
+ _("invalid name '%s': maximum length is 1024"), key);
return FALSE;
}
{
g_set_error_literal (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT,
- _("can not add keys to a 'list-of' schema"));
+ _("cannot add keys to a 'list-of' schema"));
return NULL;
}
GHashTable *flags_table; /* string -> EnumState */
GHashTable *enum_table; /* string -> EnumState */
+ GSList *this_file_schemas; /* strings: <schema>s in this file */
+ GSList *this_file_flagss; /* strings: <flags>s in this file */
+ GSList *this_file_enums; /* strings: <enum>s in this file */
+
gchar *schemalist_domain; /* the <schemalist> gettext domain */
SchemaState *schema_state; /* non-NULL when inside <schema> */
GError **error)
{
SchemaState *extends;
+ gchar *my_id;
if (g_hash_table_lookup (state->schema_table, id))
{
{
g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT,
- _("<schema id='%s'> extends not yet "
- "existing schema '%s'"), id, extends_name);
+ _("<schema id='%s'> extends not yet existing "
+ "schema '%s'"), id, extends_name);
return;
}
}
{
g_set_error (error, G_MARKUP_ERROR,
G_MARKUP_ERROR_INVALID_CONTENT,
- _("<schema id='%s'> is list of not yet "
- "existing schema '%s'"), id, list_of);
+ _("<schema id='%s'> is list of not yet existing "
+ "schema '%s'"), id, list_of);
return;
}
return;
}
+ if (path && (g_str_has_prefix (path, "/apps/") ||
+ g_str_has_prefix (path, "/desktop/") ||
+ g_str_has_prefix (path, "/system/")))
+ g_printerr ("warning: Schema '%s' has path '%s'. Paths starting with "
+ "'/apps/', '/desktop/' or '/system/' are deprecated.\n", id, path);
+
state->schema_state = schema_state_new (path, gettext_domain,
extends, extends_name, list_of);
- g_hash_table_insert (state->schema_table, g_strdup (id),
- state->schema_state);
+
+ my_id = g_strdup (id);
+ state->this_file_schemas = g_slist_prepend (state->this_file_schemas, my_id);
+ g_hash_table_insert (state->schema_table, my_id, state->schema_state);
}
static void
gboolean is_flags,
GError **error)
{
+ GSList **list = is_flags ? &state->this_file_flagss : &state->this_file_enums;
GHashTable *table = is_flags ? state->flags_table : state->enum_table;
+ gchar *my_id;
if (g_hash_table_lookup (table, id))
{
}
state->enum_state = enum_state_new (is_flags);
- g_hash_table_insert (table, g_strdup (id), state->enum_state);
+
+ my_id = g_strdup (id);
+ *list = g_slist_prepend (*list, my_id);
+ g_hash_table_insert (table, my_id, state->enum_state);
}
/* GMarkup Parser Functions {{{1 */
OPTIONAL | STRING, "gettext-domain", &gettext_domain,
OPTIONAL | STRING, "extends", &extends,
OPTIONAL | STRING, "list-of", &list_of))
- parse_state_start_schema (state, id, path, gettext_domain,
+ parse_state_start_schema (state, id, path,
+ gettext_domain ? gettext_domain
+ : state->schemalist_domain,
extends, list_of, error);
return;
}
else if (strcmp (element_name, "range") == 0)
{
const gchar *min, *max;
- if (COLLECT (STRING, "min", &min, STRING, "max", &max))
+ if (COLLECT (STRING | OPTIONAL, "min", &min,
+ STRING | OPTIONAL, "max", &max))
key_state_set_range (state->key_state, min, max, error);
return;
}
element_name, container);
else
g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
- _("Element <%s> not allowed at toplevel"), element_name);
+ _("Element <%s> not allowed at the top level"), element_name);
}
/* 2}}} */
/* End element {{{2 */
schema_state_end (SchemaState **state_ptr,
GError **error)
{
- SchemaState *state;
-
- state = *state_ptr;
*state_ptr = NULL;
}
GError **error)
{
ParseState *state = user_data;
- gsize i;
- for (i = 0; i < text_len; i++)
- if (!g_ascii_isspace (text[i]))
- {
- if (state->string)
- g_string_append_len (state->string, text, text_len);
-
- else
- g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
- _("text may not appear inside <%s>"),
- g_markup_parse_context_get_element (context));
+ if (state->string)
+ {
+ /* we are expecting a string, so store the text data.
+ *
+ * we store the data verbatim here and deal with whitespace
+ * later on. there are two reasons for that:
+ *
+ * 1) whitespace is handled differently depending on the tag
+ * type.
+ *
+ * 2) we could do leading whitespace removal by refusing to
+ * insert it into state->string if it's at the start, but for
+ * trailing whitespace, we have no idea if there is another
+ * text() call coming or not.
+ */
+ g_string_append_len (state->string, text, text_len);
+ }
+ else
+ {
+ /* string is not expected: accept (and ignore) pure whitespace */
+ gsize i;
- break;
- }
+ for (i = 0; i < text_len; i++)
+ if (!g_ascii_isspace (text[i]))
+ {
+ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+ _("text may not appear inside <%s>"),
+ g_markup_parse_context_get_element (context));
+ break;
+ }
+ }
}
/* Write to GVDB {{{1 */
typedef struct
{
+ GHashTable *schema_table;
+ GvdbPair root_pair;
+} WriteToFileData;
+
+typedef struct
+{
+ GHashTable *schema_table;
GvdbPair pair;
gboolean l10n;
} OutputSchemaData;
if (state->l10n)
data->l10n = TRUE;
+
+ if (state->child_schema &&
+ !g_hash_table_lookup (data->schema_table, state->child_schema))
+ g_printerr ("warning: undefined reference to <schema id='%s'/>\n",
+ state->child_schema);
}
static void
gpointer value,
gpointer user_data)
{
+ WriteToFileData *wtf_data = user_data;
OutputSchemaData data;
GvdbPair *root_pair;
SchemaState *state;
id = key;
state = value;
- root_pair = user_data;
+ root_pair = &wtf_data->root_pair;
+ data.schema_table = wtf_data->schema_table;
gvdb_pair_init (&data.pair);
data.l10n = FALSE;
if (state->list_of)
gvdb_hash_table_insert_string (data.pair.table, ".list-of",
- state->extends_name);
+ state->list_of);
if (data.l10n)
gvdb_hash_table_insert_string (data.pair.table,
const gchar *filename,
GError **error)
{
+ WriteToFileData data;
gboolean success;
- GvdbPair pair;
- gvdb_pair_init (&pair);
+ data.schema_table = schema_table;
+
+ gvdb_pair_init (&data.root_pair);
- g_hash_table_foreach (schema_table, output_schema, &pair);
+ g_hash_table_foreach (schema_table, output_schema, &data);
- success = gvdb_table_write_contents (pair.table, filename,
+ success = gvdb_table_write_contents (data.root_pair.table, filename,
G_BYTE_ORDER != G_LITTLE_ENDIAN,
error);
- g_hash_table_unref (pair.table);
+ g_hash_table_unref (data.root_pair.table);
return success;
}
/* Parser driver {{{1 */
static GHashTable *
-parse_gschema_files (gchar **files,
- GError **error)
+parse_gschema_files (gchar **files,
+ gboolean strict)
{
GMarkupParser parser = { start_element, end_element, text };
ParseState state = { 0, };
const gchar *filename;
+ GError *error = NULL;
state.enum_table = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, enum_state_free);
gchar *contents;
gsize size;
+ if (!g_file_get_contents (filename, &contents, &size, &error))
+ {
+ fprintf (stderr, "%s\n", error->message);
+ g_clear_error (&error);
+ continue;
+ }
+
context = g_markup_parse_context_new (&parser,
- G_MARKUP_PREFIX_ERROR_POSITION,
+ G_MARKUP_TREAT_CDATA_AS_TEXT |
+ G_MARKUP_PREFIX_ERROR_POSITION |
+ G_MARKUP_IGNORE_QUALIFIED,
&state, NULL);
- if (!g_file_get_contents (filename, &contents, &size, error))
- return NULL;
- if (!g_markup_parse_context_parse (context, contents, size, error))
+ if (!g_markup_parse_context_parse (context, contents, size, &error) ||
+ !g_markup_parse_context_end_parse (context, &error))
{
- g_prefix_error (error, "%s: ", filename);
- return NULL;
- }
+ GSList *item;
- if (!g_markup_parse_context_end_parse (context, error))
- {
- g_prefix_error (error, "%s: ", filename);
- return NULL;
+ /* back out any changes from this file */
+ for (item = state.this_file_schemas; item; item = item->next)
+ g_hash_table_remove (state.schema_table, item->data);
+
+ for (item = state.this_file_flagss; item; item = item->next)
+ g_hash_table_remove (state.flags_table, item->data);
+
+ for (item = state.this_file_enums; item; item = item->next)
+ g_hash_table_remove (state.enum_table, item->data);
+
+ /* let them know */
+ fprintf (stderr, "%s: %s. ", filename, error->message);
+ g_clear_error (&error);
+
+ if (strict)
+ {
+ /* Translators: Do not translate "--strict". */
+ fprintf (stderr, _("--strict was specified; exiting.\n"));
+ g_hash_table_unref (state.schema_table);
+ g_hash_table_unref (state.flags_table);
+ g_hash_table_unref (state.enum_table);
+
+ return NULL;
+ }
+ else
+ fprintf (stderr, _("This entire file has been ignored.\n"));
}
+ /* cleanup */
g_markup_parse_context_free (context);
+ g_slist_free (state.this_file_schemas);
+ g_slist_free (state.this_file_flagss);
+ g_slist_free (state.this_file_enums);
+ state.this_file_schemas = NULL;
+ state.this_file_flagss = NULL;
+ state.this_file_enums = NULL;
}
+ g_hash_table_unref (state.flags_table);
g_hash_table_unref (state.enum_table);
return state.schema_table;
static gboolean
set_overrides (GHashTable *schema_table,
gchar **files,
- GError **error)
+ gboolean strict)
{
const gchar *filename;
+ GError *error = NULL;
while ((filename = *files++))
{
gint i;
key_file = g_key_file_new ();
- if (!g_key_file_load_from_file (key_file, filename, 0, error))
+ if (!g_key_file_load_from_file (key_file, filename, 0, &error))
{
+ fprintf (stderr, "%s: %s. ", filename, error->message);
g_key_file_free (key_file);
+ g_clear_error (&error);
+ if (!strict)
+ {
+ fprintf (stderr, _("Ignoring this file.\n"));
+ continue;
+ }
+
+ fprintf (stderr, _("--strict was specified; exiting.\n"));
return FALSE;
}
schema = g_hash_table_lookup (schema_table, group);
if (schema == NULL)
- {
- g_set_error (error, G_KEY_FILE_ERROR,
- G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
- _("No such schema `%s' specified in "
- "override file `%s'"), group, filename);
- g_key_file_free (key_file);
- g_strfreev (groups);
-
- return FALSE;
- }
+ /* Having the schema not be installed is expected to be a
+ * common case. Don't even emit an error message about
+ * that.
+ */
+ continue;
keys = g_key_file_get_keys (key_file, group, NULL, NULL);
g_assert (keys != NULL);
if (state == NULL)
{
- g_set_error (error, G_KEY_FILE_ERROR,
- G_KEY_FILE_ERROR_KEY_NOT_FOUND,
- _("No such key `%s' in schema `%s' as "
- "specified in override file `%s'"),
- key, group, filename);
+ fprintf (stderr, _("No such key '%s' in schema '%s' as "
+ "specified in override file '%s'"),
+ key, group, filename);
+
+ if (!strict)
+ {
+ fprintf (stderr, _("; ignoring override for this key.\n"));
+ continue;
+ }
+
+ fprintf (stderr, _(" and --strict was specified; exiting.\n"));
g_key_file_free (key_file);
g_strfreev (groups);
g_strfreev (keys);
g_assert (string != NULL);
value = g_variant_parse (state->type, string,
- NULL, NULL, error);
+ NULL, NULL, &error);
if (value == NULL)
{
+ fprintf (stderr, _("error parsing key '%s' in schema '%s' "
+ "as specified in override file '%s': "
+ "%s."),
+ key, group, filename, error->message);
+
+ g_clear_error (&error);
+ g_free (string);
+
+ if (!strict)
+ {
+ fprintf (stderr, _("Ignoring override for this key.\n"));
+ continue;
+ }
+
+ fprintf (stderr, _("--strict was specified; exiting.\n"));
g_key_file_free (key_file);
g_strfreev (groups);
g_strfreev (keys);
- g_free (string);
return FALSE;
}
if (g_variant_compare (value, state->minimum) < 0 ||
g_variant_compare (value, state->maximum) > 0)
{
- g_set_error (error, G_MARKUP_ERROR,
- G_MARKUP_ERROR_INVALID_CONTENT,
- _("override for key `%s' in schema `%s' in "
- "override file `%s' is out of the range "
- "given in the schema"),
- key, group, filename);
+ fprintf (stderr,
+ _("override for key '%s' in schema '%s' in "
+ "override file '%s' is outside the range "
+ "given in the schema"),
+ key, group, filename);
- g_key_file_free (key_file);
g_variant_unref (value);
+ g_free (string);
+
+ if (!strict)
+ {
+ fprintf (stderr, _("; ignoring override for this key.\n"));
+ continue;
+ }
+
+ fprintf (stderr, _(" and --strict was specified; exiting.\n"));
+ g_key_file_free (key_file);
g_strfreev (groups);
g_strfreev (keys);
- g_free (string);
return FALSE;
}
{
if (!is_valid_choices (value, state->strinfo))
{
- g_set_error (error, G_MARKUP_ERROR,
- G_MARKUP_ERROR_INVALID_CONTENT,
- _("override for key `%s' in schema `%s' in "
- "override file `%s' is not in the list "
- "of valid choices"),
- key, group, filename);
+ fprintf (stderr,
+ _("override for key '%s' in schema '%s' in "
+ "override file '%s' is not in the list "
+ "of valid choices"),
+ key, group, filename);
- g_key_file_free (key_file);
g_variant_unref (value);
+ g_free (string);
+
+ if (!strict)
+ {
+ fprintf (stderr, _("; ignoring override for this key.\n"));
+ continue;
+ }
+
+ fprintf (stderr, _(" and --strict was specified; exiting.\n"));
+ g_key_file_free (key_file);
g_strfreev (groups);
g_strfreev (keys);
- g_free (string);
return FALSE;
}
g_variant_unref (state->default_value);
state->default_value = value;
+ g_free (string);
}
-
g_strfreev (keys);
}
gchar *srcdir;
gchar *targetdir = NULL;
gchar *target;
- gboolean uninstall = FALSE;
gboolean dry_run = FALSE;
+ gboolean strict = FALSE;
gchar **schema_files = NULL;
gchar **override_files = NULL;
GOptionContext *context;
GOptionEntry entries[] = {
{ "targetdir", 0, 0, G_OPTION_ARG_FILENAME, &targetdir, N_("where to store the gschemas.compiled file"), N_("DIRECTORY") },
+ { "strict", 0, 0, G_OPTION_ARG_NONE, &strict, N_("Abort on any errors in schemas"), NULL },
{ "dry-run", 0, 0, G_OPTION_ARG_NONE, &dry_run, N_("Do not write the gschema.compiled file"), NULL },
- { "uninstall", 0, 0, G_OPTION_ARG_NONE, &uninstall, N_("This option will be removed soon.") },
{ "allow-any-name", 0, 0, G_OPTION_ARG_NONE, &allow_any_name, N_("Do not enforce key name restrictions") },
/* These options are only for use in the gschema-compile tests */
{ NULL }
};
+#ifdef G_OS_WIN32
+ gchar *tmp;
+#endif
+
setlocale (LC_ALL, "");
+ textdomain (GETTEXT_PACKAGE);
+
+#ifdef G_OS_WIN32
+ tmp = _glib_get_locale_dir ();
+ bindtextdomain (GETTEXT_PACKAGE, tmp);
+ g_free (tmp);
+#else
+ bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
+#endif
+
+#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+#endif
context = g_option_context_new (N_("DIRECTORY"));
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
error = NULL;
if (!g_option_context_parse (context, &argc, &argv, &error))
{
- fprintf (stderr, "%s", error->message);
+ fprintf (stderr, "%s\n", error->message);
return 1;
}
if (files->len == 0)
{
- fprintf (stderr, _("No schema files found: "));
+ fprintf (stdout, _("No schema files found: "));
if (g_unlink (target))
- fprintf (stderr, _("doing nothing.\n"));
+ fprintf (stdout, _("doing nothing.\n"));
else
- fprintf (stderr, _("removed existing output file.\n"));
+ fprintf (stdout, _("removed existing output file.\n"));
return 0;
}
override_files = (gchar **) g_ptr_array_free (overrides, FALSE);
}
+ if ((table = parse_gschema_files (schema_files, strict)) == NULL)
+ {
+ g_free (target);
+ return 1;
+ }
+
+ if (override_files != NULL &&
+ !set_overrides (table, override_files, strict))
+ {
+ g_free (target);
+ return 1;
+ }
- if (!(table = parse_gschema_files (schema_files, &error)) ||
- (override_files != NULL && !set_overrides (table, override_files, &error)) ||
- (!dry_run && !write_to_file (table, target, &error)))
+ if (!dry_run && !write_to_file (table, target, &error))
{
fprintf (stderr, "%s\n", error->message);
+ g_free (target);
return 1;
}