[gjs] Add gjs_parse_args



commit dd2d1590e6ea650cbc45453997198d92633a147f
Author: Colin Walters <walters verbum org>
Date:   Wed Aug 5 20:14:52 2009 -0400

    Add gjs_parse_args
    
    Add a utility function to extract arguments from JavaScript to
    C, generating useful error messages automatically.
    
    To implement this, extract out the core of the string conversion
    function gjs_string_to_utf8 into gjs_try_string_to_utf8, which
    takes a GError parameter.  This allows us to append any
    conversion error message into our generated exception sanely,
    i.e. without having to check for and parse the currently
    pending JS exception.
    
    Do similar for gjs_string_to_filename.

 gjs/jsapi-util-error.c  |   30 ++++++++++
 gjs/jsapi-util-string.c |  122 +++++++++++++++++++++++++----------------
 gjs/jsapi-util.c        |  139 +++++++++++++++++++++++++++++++++++++++++++++++
 gjs/jsapi-util.h        |   33 +++++++++++
 4 files changed, 277 insertions(+), 47 deletions(-)
---
diff --git a/gjs/jsapi-util-error.c b/gjs/jsapi-util-error.c
index 127e425..2e1e58c 100644
--- a/gjs/jsapi-util-error.c
+++ b/gjs/jsapi-util-error.c
@@ -162,6 +162,36 @@ gjs_throw(JSContext       *context,
     va_end(args);
 }
 
+/**
+ * gjs_throw_literal:
+ * 
+ * Similar to gjs_throw(), but does not treat its argument as
+ * a format string.
+ */
+void
+gjs_throw_literal(JSContext       *context,
+                  const char      *string)
+{
+    gjs_throw(context, "%s", string);
+}
+
+/**
+ * gjs_throw_g_error:
+ * 
+ * Convert a GError into a JavaScript Exception, and
+ * frees the GError.  Like gjs_throw(), will not overwrite
+ * an already pending exception.
+ */
+void
+gjs_throw_g_error (JSContext       *context,
+                   GError          *error)
+{
+    if (error == NULL)
+        return;
+    gjs_throw_literal(context, error->message);
+    g_error_free (error);
+}
+
 #if GJS_BUILD_TESTS
 static void
 test_error_reporter(JSContext     *context,
diff --git a/gjs/jsapi-util-string.c b/gjs/jsapi-util-string.c
index 70667e8..2e953c8 100644
--- a/gjs/jsapi-util-string.c
+++ b/gjs/jsapi-util-string.c
@@ -27,46 +27,46 @@
 
 #include "jsapi-util.h"
 
-JSBool
-gjs_string_to_utf8(JSContext  *context,
-                   const jsval string_val,
-                   char      **utf8_string_p)
+gboolean
+gjs_try_string_to_utf8 (JSContext  *context,
+                        const jsval string_val,
+                        char      **utf8_string_p,
+                        GError    **error)
 {
     jschar *s;
     size_t s_length;
     char *utf8_string;
     long read_items;
     long utf8_length;
-    GError *error;
+    GError *convert_error = NULL;
 
     if (!JSVAL_IS_STRING(string_val)) {
-        gjs_throw(context,
-                     "Object is not a string, cannot convert to UTF-8");
-        return JS_FALSE;
+        g_set_error_literal(error, GJS_UTIL_ERROR, GJS_UTIL_ERROR_ARGUMENT_TYPE_MISMATCH,
+                            "Object is not a string, cannot convert to UTF-8");
+        return FALSE;
     }
 
     s = JS_GetStringChars(JSVAL_TO_STRING(string_val));
     s_length = JS_GetStringLength(JSVAL_TO_STRING(string_val));
 
-    error = NULL;
     utf8_string = g_utf16_to_utf8(s,
                                   (glong)s_length,
                                   &read_items, &utf8_length,
-                                  &error);
+                                  &convert_error);
 
     if (!utf8_string) {
-        gjs_throw(context,
-                  "Failed to convert JS string to "
-                  "UTF-8: %s",
-                  error->message);
-        g_error_free(error);
-        return JS_FALSE;
+        g_set_error(error, GJS_UTIL_ERROR, GJS_UTIL_ERROR_ARGUMENT_INVALID,
+                    "Failed to convert JS string to UTF-8: %s",
+                    convert_error->message);
+        g_error_free(convert_error);
+        return FALSE;
     }
 
     if ((size_t)read_items != s_length) {
-        gjs_throw(context, "JS string contains embedded NULs");
+        g_set_error_literal(error, GJS_UTIL_ERROR, GJS_UTIL_ERROR_ARGUMENT_INVALID,
+                            "JS string contains embedded NULs");
         g_free(utf8_string);
-        return JS_FALSE;
+        return FALSE;
     }
 
     /* Our assumption is that the string is being converted to UTF-8
@@ -79,13 +79,29 @@ gjs_string_to_utf8(JSContext  *context,
      * case of all-ASCII.
      */
     if (!g_utf8_validate (utf8_string, utf8_length, NULL)) {
-        gjs_throw(context, "JS string contains invalid Unicode characters");
+        g_set_error_literal(error, GJS_UTIL_ERROR, GJS_UTIL_ERROR_ARGUMENT_INVALID,
+                            "JS string contains invalid Unicode characters");
         g_free(utf8_string);
-        return JS_FALSE;
+        return FALSE;
     }
 
     *utf8_string_p = utf8_string;
-    return JS_TRUE;
+    return TRUE;
+}
+
+JSBool
+gjs_string_to_utf8 (JSContext  *context,
+                    const jsval string_val,
+                    char      **utf8_string_p)
+{
+  GError *error = NULL;
+  
+  if (!gjs_try_string_to_utf8(context, string_val, utf8_string_p, &error))
+    {
+      gjs_throw_g_error(context, error);
+      return JS_FALSE;
+    }
+  return JS_TRUE;
 }
 
 JSBool
@@ -131,37 +147,49 @@ gjs_string_from_utf8(JSContext  *context,
     return JS_TRUE;
 }
 
+gboolean
+gjs_try_string_to_filename(JSContext    *context,
+                           const jsval   filename_val,
+                           char        **filename_string_p,
+                           GError      **error)
+{
+  gchar *tmp, *filename_string;
+
+  /* gjs_string_to_filename verifies that filename_val is a string */
+
+  if (!gjs_try_string_to_utf8(context, filename_val, &tmp, error)) {
+      /* exception already set */
+      return JS_FALSE;
+  }
+  
+  error = NULL;
+  filename_string = g_filename_from_utf8(tmp, -1, NULL, NULL, error);
+  if (!filename_string) {
+    g_free(tmp);
+    return FALSE;
+  }
+
+  *filename_string_p = filename_string;
+  
+  g_free(tmp);
+  return TRUE;  
+}
+
 JSBool
 gjs_string_to_filename(JSContext    *context,
                        const jsval   filename_val,
                        char        **filename_string_p)
 {
-    GError *error;
-    gchar *tmp, *filename_string;
-
-    /* gjs_string_to_filename verifies that filename_val is a string */
-
-    if (!gjs_string_to_utf8(context, filename_val, &tmp)) {
-        /* exception already set */
-        return JS_FALSE;
-    }
-    
-    error = NULL;
-    filename_string = g_filename_from_utf8(tmp, -1, NULL, NULL, &error);
-    if (error) {
-        gjs_throw(context,
-                  "Could not convert filename '%s' to UTF8: '%s'",
-                  tmp,
-                  error->message);
-        g_error_free(error);
-        g_free(tmp);
-        return JS_FALSE;
-    }
-
-    *filename_string_p = filename_string;
-    
-    g_free(tmp);
-    return JS_TRUE;
+  GError *error = NULL;
+
+  if (!gjs_try_string_to_filename(context, filename_val, filename_string_p, &error)) {
+      gjs_throw(context,
+                "Could not convert filename to UTF8: '%s'",
+                error->message);
+      g_error_free(error);
+      return JS_FALSE;
+  }
+  return JS_TRUE;
 }
 
 JSBool
diff --git a/gjs/jsapi-util.c b/gjs/jsapi-util.c
index 629c9d3..fb7e750 100644
--- a/gjs/jsapi-util.c
+++ b/gjs/jsapi-util.c
@@ -1,6 +1,7 @@
 /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
 /*
  * Copyright (c) 2008  litl, LLC
+ * Copyright (c) 2009 Red Hat, Inc.
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to
@@ -33,6 +34,11 @@
 #include <string.h>
 #include <jscntxt.h>
 
+GQuark 
+gjs_util_error_quark (void)
+{
+  return g_quark_from_static_string ("gjs-util-error-quark");
+}
 
 typedef struct {
     GHashTable *dynamic_classes;
@@ -1037,3 +1043,136 @@ gjs_date_from_time_t (JSContext *context, time_t time)
     JS_LeaveLocalRootScope(context);
     return OBJECT_TO_JSVAL(date);
 }
+
+/**
+ * gjs_parse_args:
+ * @context:
+ * @function_name: The name of the function being called
+ * @format: Printf-like format specifier containing the expected arguments
+ * @argc: Number of JavaScript arguments
+ * @argv: JavaScript argument array
+ * @Varargs: for each character in @format, a pair of a char * which is the name
+ * of the argument, and a pointer to a location to store the value. The type of
+ * value stored depends on the format character, as described below.
+ * 
+ * This function is inspired by Python's PyArg_ParseTuple for those
+ * familiar with it.  It takes a format specifier which gives the
+ * types of the expected arguments, and a list of argument names and
+ * value location pairs.  The currently accepted format specifiers are:
+ * 
+ * s: A string, converted into UTF-8
+ * z: Like 's', but may be null in JavaScript (which appears as NULL in C)
+ * F: A string, converted into "filename encoding" (i.e. active locale)
+ * i: A number, will be converted to a C "gint32"
+ * u: A number, converted into a C "guint32"
+ */
+JSBool
+gjs_parse_args (JSContext  *context,
+                const char *function_name,
+                const char *format,
+                uintN      argc,
+                jsval     *argv,
+                ...)
+{
+    guint i;
+    const char *fmt_iter;
+    va_list args;
+    GError *arg_error = NULL;
+    guint n_unwind = 0;
+#define MAX_UNWIND_STRINGS 16
+    gpointer unwind_strings[MAX_UNWIND_STRINGS];
+  
+    va_start (args, argv);
+  
+    for (i = 0, fmt_iter = format; *fmt_iter; fmt_iter++, i++) {
+        const char *argname = va_arg (args, char *);
+        gpointer arg_location = va_arg (args, gpointer);
+        jsval js_value;
+        const char *arg_error_message = NULL;
+        
+        if (i >= argc) {
+            gjs_throw(context, "Error invoking %s: Expected %d arguments, got %d", function_name,
+                      strlen (format), argc);
+            goto error_unwind;
+        }
+        
+        g_return_val_if_fail (argname != NULL, JS_FALSE);
+        g_return_val_if_fail (arg_location != NULL, JS_FALSE);
+        
+        js_value = argv[i];
+        
+        switch (*fmt_iter) {
+            case 's':
+            case 'z': {
+                char **arg = arg_location;
+                
+                if (*fmt_iter == 'z' && JSVAL_IS_NULL(js_value)) {
+                    *arg = NULL;
+                } else {
+                    if (gjs_try_string_to_utf8 (context, js_value, arg, &arg_error)) {
+                        unwind_strings[n_unwind++] = *arg;
+                        g_assert(n_unwind < MAX_UNWIND_STRINGS);
+                    }
+                }    
+            }
+            break;
+            case 'F': {
+                char **arg = arg_location;
+                
+                if (gjs_try_string_to_filename (context, js_value, arg, &arg_error)) {
+                    unwind_strings[n_unwind++] = *arg;
+                    g_assert(n_unwind < MAX_UNWIND_STRINGS);
+                }
+            }
+            break;
+            case 'i': {
+                if (!JS_ValueToInt32(context, js_value, (gint32*) arg_location)) {
+                    /* Our error message is going to be more useful */
+                    JS_ClearPendingException(context);
+                    arg_error_message = "Couldn't convert to integer";
+                }
+            }
+            break;
+            case 'u': {
+                gdouble num;
+                if (!JS_ValueToNumber(context, js_value, &num)) {
+                    /* Our error message is going to be more useful */
+                    JS_ClearPendingException(context);
+                    arg_error_message = "Couldn't convert to unsigned integer";
+                } else if (num > G_MAXUINT32 || num < 0) {
+                    arg_error_message = "Value is out of range";
+                } else {
+                    *((guint32*) arg_location) = num;
+                }
+            }
+            break;
+        }
+        
+        if (arg_error != NULL)
+            arg_error_message = arg_error->message;
+        
+        if (arg_error_message != NULL) {
+            gjs_throw(context, "Error invoking %s, at argument %d (%s): %s", function_name, 
+                      i, argname, arg_error_message);
+            g_clear_error (&arg_error);
+            goto error_unwind;
+        }
+    }
+    
+    if (i < argc) {
+        gjs_throw(context, "Error invoking %s: Expected %d arguments, got %d", function_name,
+                  strlen (format), argc);
+        goto error_unwind;
+    }
+
+    va_end (args);
+    return JS_TRUE;
+
+  error_unwind:
+    va_end (args);
+    /* We still own the strings in the error case, free any we converted */
+    for (i = 0; i < n_unwind; i++) {
+        g_free (unwind_strings[i]);
+    }
+    return JS_FALSE;
+}
diff --git a/gjs/jsapi-util.h b/gjs/jsapi-util.h
index d75ee4b..b8e2eae 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -33,6 +33,16 @@
 
 G_BEGIN_DECLS
 
+#define GJS_UTIL_ERROR gjs_util_error_quark ()
+GQuark gjs_util_error_quark (void);
+enum {
+  GJS_UTIL_ERROR_NONE,
+  GJS_UTIL_ERROR_ARGUMENT_INVALID,
+  GJS_UTIL_ERROR_ARGUMENT_UNDERFLOW,
+  GJS_UTIL_ERROR_ARGUMENT_OVERFLOW,
+  GJS_UTIL_ERROR_ARGUMENT_TYPE_MISMATCH
+};
+
 typedef struct GjsRootedArray GjsRootedArray;
 
 /* Flags that should be set on properties exported from native code modules.
@@ -153,6 +163,10 @@ JSObject*   gjs_define_string_array          (JSContext       *context,
 void        gjs_throw                        (JSContext       *context,
                                               const char      *format,
                                               ...)  G_GNUC_PRINTF (2, 3);
+void        gjs_throw_literal                (JSContext       *context,
+                                              const char      *string);
+void        gjs_throw_g_error                (JSContext       *context,
+                                              GError          *error);
 JSBool      gjs_log_exception                (JSContext       *context,
                                               char           **message_p);
 JSBool      gjs_log_and_keep_exception       (JSContext       *context,
@@ -196,6 +210,7 @@ JSBool      gjs_delete_prop_verbose_stub     (JSContext       *context,
                                               JSObject        *obj,
                                               jsval            id,
                                               jsval           *value_p);
+
 JSBool      gjs_string_to_utf8               (JSContext       *context,
                                               const            jsval string_val,
                                               char           **utf8_string_p);
@@ -231,6 +246,13 @@ const char* gjs_get_type_name                (jsval            value);
 
 jsval       gjs_date_from_time_t             (JSContext *context, time_t time);
 
+JSBool      gjs_parse_args                   (JSContext  *context,
+                                              const char *function_name,
+                                              const char *format,
+                                              uintN      argc,
+                                              jsval     *argv,
+                                              ...);
+
 GjsRootedArray*   gjs_rooted_array_new        (void);
 void              gjs_rooted_array_append     (JSContext        *context,
                                                GjsRootedArray *array,
@@ -256,6 +278,17 @@ void              gjs_unroot_value_locations  (JSContext        *context,
                                                jsval            *locations,
                                                int               n_locations);
 
+/* Functions intended for more "internal" use */
+
+gboolean gjs_try_string_to_filename           (JSContext    *context,
+                                               const jsval   filename_val,
+                                               char        **filename_string_p,
+                                               GError      **error);
+
+gboolean    gjs_try_string_to_utf8            (JSContext       *context,
+                                               const            jsval string_val,
+                                               char           **utf8_string_p,
+                                               GError         **error);
 
 G_END_DECLS
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]