ext/i18nema/i18nema.c in i18nema-0.0.4 vs ext/i18nema/i18nema.c in i18nema-0.0.5
- old
+ new
@@ -1,21 +1,23 @@
#include <ruby.h>
#include <ruby/encoding.h>
-#include <syck.h>
-#include "uthash.h"
+#include "vendor/syck.h"
+#include "vendor/uthash.h"
VALUE I18nema = Qnil,
I18nemaBackend = Qnil,
I18nemaBackendLoadError = Qnil;
struct i_object;
struct i_key_value;
static VALUE array_to_rarray(struct i_object *array);
static VALUE hash_to_rhash(struct i_object *hash);
static void merge_hash(struct i_object *hash, struct i_object *other_hash);
-static void delete_hash(struct i_key_value **hash, int delete_child_objects);
-static void delete_object(struct i_object *object, int delete_child_objects);
+static void delete_hash(struct i_key_value **hash, int recurse);
+static void delete_object(struct i_object *object, int recurse);
+static void delete_object_r(struct i_object *object);
+static VALUE normalize_key(VALUE self, VALUE key, VALUE separator);
enum i_object_type {
i_type_none,
i_type_string,
i_type_array,
@@ -103,19 +105,43 @@
rb_hash_aset(result, ID2SYM(rb_intern(handle->key)), i_object_to_robject(handle->value));
return result;
}
static i_object_t*
-root_object_get(VALUE self)
+i_object_get(VALUE self, const char *iv)
{
- i_object_t *root_object;
- VALUE translations;
- translations = rb_iv_get(self, "@translations");
- Data_Get_Struct(translations, i_object_t, root_object);
- return root_object;
+ i_object_t *object;
+ VALUE wrapped;
+ wrapped = rb_iv_get(self, iv);
+ Data_Get_Struct(wrapped, i_object_t, object);
+ return object;
}
+static i_object_t*
+translations_get(VALUE self)
+{
+ return i_object_get(self, "@translations");
+}
+
+static i_object_t*
+normalized_key_cache_get(VALUE self)
+{
+ return i_object_get(self, "@normalized_key_cache");
+}
+
+static i_object_t*
+hash_get(i_object_t *current, VALUE *keys, int num_keys)
+{
+ i_key_value_t *kv = NULL;
+ for (int i = 0; i < num_keys && current != NULL && current->type == i_type_hash; i++) {
+ Check_Type(keys[i], T_STRING);
+ HASH_FIND_STR(current->data.hash, StringValueCStr(keys[i]), kv);
+ current = kv == NULL ? NULL : kv->value;
+ }
+ return current;
+}
+
/*
* call-seq:
* backend.direct_lookup([part]+) -> localized_str
*
* Returns the translation(s) found under the specified key.
@@ -125,80 +151,68 @@
*/
static VALUE
direct_lookup(int argc, VALUE *argv, VALUE self)
{
- i_object_t *result = root_object_get(self);;
- i_key_value_t *kv = NULL;
- VALUE rs;
- char *s;
-
- for (int i = 0; i < argc && result != NULL && result->type == i_type_hash; i++) {
- rs = rb_funcall(argv[i], s_to_s, 0);
- s = StringValueCStr(rs);
- HASH_FIND_STR(result->data.hash, s, kv);
- result = kv == NULL ? NULL : kv->value;
- }
-
- return i_object_to_robject(result);
+ i_object_t *translations = translations_get(self);
+ return i_object_to_robject(hash_get(translations, argv, argc));
}
static void
-empty_object(i_object_t *object, int delete_child_objects)
+empty_object(i_object_t *object, int recurse)
{
if (object == NULL)
return;
switch (object->type) {
case i_type_array:
- if (delete_child_objects) {
+ if (recurse)
for (unsigned long i = 0; i < object->size; i++)
- delete_object(object->data.array[i], 1);
- }
+ delete_object_r(object->data.array[i]);
xfree(object->data.array);
break;
case i_type_hash:
- delete_hash(&object->data.hash, delete_child_objects);
+ delete_hash(&object->data.hash, recurse);
break;
case i_type_none:
break;
default:
xfree(object->data.string);
break;
}
}
static void
-delete_object_r(i_object_t *object)
+delete_object(i_object_t *object, int recurse)
{
- delete_object(object, 1);
+ empty_object(object, recurse);
+ if (object->type != i_type_null && object->type != i_type_true && object->type != i_type_false)
+ xfree(object);
}
static void
-delete_object(i_object_t *object, int delete_child_objects)
+delete_object_r(i_object_t *object)
{
- empty_object(object, delete_child_objects);
- if (object->type != i_type_null && object->type != i_type_true && object->type != i_type_false)
- xfree(object);
+ delete_object(object, 1);
}
static void
delete_key_value(i_key_value_t *kv, int delete_value)
{
if (delete_value)
- delete_object(kv->value, 1);
+ delete_object_r(kv->value);
xfree(kv->key);
xfree(kv);
}
static void
-delete_hash(i_key_value_t **hash, int delete_child_objects)
+delete_hash(i_key_value_t **hash, int recurse)
{
i_key_value_t *kv, *tmp;
HASH_ITER(hh, *hash, kv, tmp) {
HASH_DEL(*hash, kv);
- delete_key_value(kv, delete_child_objects);
+ delete_key_value(kv, recurse);
}
}
static void
add_key_value(i_key_value_t **hash, i_key_value_t *kv)
@@ -225,20 +239,20 @@
HASH_ITER(hh, other_hash->data.hash, kv, tmp) {
HASH_DEL(other_hash->data.hash, kv);
add_key_value(&hash->data.hash, kv);
}
- delete_object(other_hash, 1);
+ delete_object_r(other_hash);
}
static int
delete_syck_st_entry(char *key, char *value, char *arg)
{
i_object_t *object = (i_object_t *)value;
// key object whose string we have yoinked into a kv
if (object->type == i_type_none)
- delete_object(object, 1);
+ delete_object_r(object);
return ST_DELETE;
}
static int
delete_syck_object(char *key, char *value, char *arg)
@@ -268,22 +282,57 @@
sprintf(error, "bad anchor `%s'", anchor);
handle_syck_error(parser, error);
return NULL;
}
+static char*
+new_string(char *orig, long len)
+{
+ char *str = xmalloc(len + 1);
+ strncpy(str, orig, len);
+ str[len] = '\0';
+ return str;
+}
+
static i_object_t*
new_string_object(char *str, long len)
{
i_object_t *object = ALLOC(i_object_t);
object->type = i_type_string;
object->size = len;
- object->data.string = xmalloc(len + 1);
- strncpy(object->data.string, str, len);
- object->data.string[len] = '\0';
+ object->data.string = new_string(str, len);
return object;
}
+static i_object_t*
+new_array_object(long size)
+{
+ i_object_t *object = ALLOC(i_object_t);
+ object->type = i_type_array;
+ object->size = size;
+ object->data.array = ALLOC_N(i_object_t*, size);
+ return object;
+}
+
+static i_object_t*
+new_hash_object()
+{
+ i_object_t *object = ALLOC(i_object_t);
+ object->type = i_type_hash;
+ object->data.hash = NULL;
+ return object;
+}
+
+static i_key_value_t*
+new_key_value(char *key, i_object_t *value)
+{
+ i_key_value_t *kv = ALLOC(i_key_value_t);
+ kv->key = key;
+ kv->value = value;
+ return kv;
+}
+
static SYMID
handle_syck_node(SyckParser *parser, SyckNode *node)
{
i_object_t *result;
SYMID oid;
@@ -313,14 +362,11 @@
// legit strings, and everything else get the string treatment (binary, int#hex, timestamp, etc.)
result = new_string_object(node->data.str->ptr, node->data.str->len);
}
break;
case syck_seq_kind:
- result = ALLOC(i_object_t);
- result->type = i_type_array;
- result->size = node->data.list->idx;
- result->data.array = ALLOC_N(i_object_t*, node->data.list->idx);
+ result = new_array_object(node->data.list->idx);
for (long i = 0; i < node->data.list->idx; i++) {
i_object_t *item = NULL;
oid = syck_seq_read(node, i);
syck_lookup_sym(parser, oid, (void **)&item);
@@ -328,26 +374,21 @@
current_translation_count++;
result->data.array[i] = item;
}
break;
case syck_map_kind:
- result = ALLOC(i_object_t);
- result->type = i_type_hash;
- result->data.hash = NULL;
+ result = new_hash_object();
for (long i = 0; i < node->data.pairs->idx; i++) {
i_object_t *key = NULL, *value = NULL;
oid = syck_map_read(node, map_key, i);
syck_lookup_sym(parser, oid, (void **)&key);
oid = syck_map_read(node, map_value, i);
syck_lookup_sym(parser, oid, (void **)&value);
- i_key_value_t *kv;
- kv = ALLOC(i_key_value_t);
- kv->key = key->data.string;
+ i_key_value_t *kv = new_key_value(key->data.string, value);
key->type = i_type_none; // so we know to free this node in delete_syck_st_entry
- kv->value = value;
if (value->type == i_type_string)
current_translation_count++;
add_key_value(&result->data.hash, kv);
}
break;
@@ -369,11 +410,11 @@
static VALUE
load_yml_string(VALUE self, VALUE yml)
{
SYMID oid;
- i_object_t *root_object = root_object_get(self);
+ i_object_t *root_object = translations_get(self);
i_object_t *new_root_object = NULL;
current_translation_count = 0;
SyckParser* parser = syck_new_parser();
syck_parser_handler(parser, handle_syck_node);
StringValue(yml);
@@ -385,11 +426,11 @@
syck_lookup_sym(parser, oid, (void **)&new_root_object);
if (parser->syms)
st_foreach(parser->syms, delete_syck_st_entry, 0);
syck_free_parser(parser);
if (new_root_object == NULL || new_root_object->type != i_type_hash) {
- delete_object(new_root_object, 1);
+ delete_object_r(new_root_object);
rb_raise(I18nemaBackendLoadError, "root yml node is not a hash");
}
merge_hash(root_object, new_root_object);
return INT2NUM(current_translation_count);
@@ -407,11 +448,11 @@
static VALUE
available_locales(VALUE self)
{
if (!RTEST(rb_iv_get(self, "@initialized")))
rb_funcall(self, s_init_translations, 0);
- i_object_t *root_object = root_object_get(self);
+ i_object_t *root_object = translations_get(self);
i_key_value_t *current = root_object->data.hash;
VALUE ary = rb_ary_new2(0);
for (; current != NULL; current = current->hh.next)
rb_ary_push(ary, rb_str_intern(rb_str_new2(current->key)));
@@ -429,26 +470,100 @@
*/
static VALUE
reload(VALUE self)
{
- i_object_t *root_object = root_object_get(self);
+ i_object_t *root_object = translations_get(self);
empty_object(root_object, 1);
rb_iv_set(self, "@initialized", Qfalse);
- root_object = NULL;
return Qtrue;
}
static VALUE
+join_array_key(VALUE self, VALUE key, VALUE separator)
+{
+ long len = RARRAY_LEN(key);
+ if (len == 0)
+ return rb_str_new("", 0);
+
+ VALUE ret = rb_ary_join(normalize_key(self, RARRAY_PTR(key)[0], separator), separator);
+ for (long i = 1; i < len; i++) {
+ rb_str_concat(ret, separator);
+ rb_str_concat(ret, rb_ary_join(normalize_key(self, RARRAY_PTR(key)[i], separator), separator));
+ }
+ return ret;
+}
+
+/*
+ * call-seq:
+ * backend.normalize_key(key, separator) -> key
+ *
+ * Normalizes and splits a key based on the separator.
+ *
+ * backend.normalize_key "asdf", "." #=> ["asdf"]
+ * backend.normalize_key "a.b.c", "." #=> ["a", "b", "c"]
+ * backend.normalize_key "a.b.c", ":" #=> ["a.b.c"]
+ * backend.normalize_key %{a b.c}, "." #=> ["a", "b", "c"]
+ */
+
+static VALUE
+normalize_key(VALUE self, VALUE key, VALUE separator)
+{
+ Check_Type(separator, T_STRING);
+
+ i_object_t *key_map = normalized_key_cache_get(self),
+ *sub_map = hash_get(key_map, &separator, 1);
+ if (sub_map == NULL) {
+ sub_map = new_hash_object();
+ char *key = new_string(RSTRING_PTR(separator), RSTRING_LEN(separator));
+ i_key_value_t *kv = new_key_value(key, sub_map);
+ add_key_value(&key_map->data.hash, kv);
+ }
+
+ if (TYPE(key) == T_ARRAY)
+ key = join_array_key(self, key, separator);
+ else if (TYPE(key) != T_STRING)
+ key = rb_funcall(key, s_to_s, 0);
+
+ i_object_t *key_frd = hash_get(sub_map, &key, 1);
+
+ if (key_frd == NULL) {
+ char *sep = StringValueCStr(separator);
+ VALUE parts = rb_str_split(key, sep);
+ long parts_len = RARRAY_LEN(parts),
+ skipped = 0;
+ key_frd = new_array_object(parts_len);
+ for (long i = 0; i < parts_len; i++) {
+ VALUE part = RARRAY_PTR(parts)[i];
+ // TODO: don't alloc for empty strings, since we discard them
+ if (RSTRING_LEN(part) == 0)
+ skipped++;
+ else
+ key_frd->data.array[i - skipped] = new_string_object(RSTRING_PTR(part), RSTRING_LEN(part));
+ }
+ key_frd->size -= skipped;
+
+ char *key_orig = new_string(RSTRING_PTR(key), RSTRING_LEN(key));
+ i_key_value_t *kv = new_key_value(key_orig, key_frd);
+ add_key_value(&sub_map->data.hash, kv);
+ }
+ return i_object_to_robject(key_frd);
+}
+
+static VALUE
initialize(VALUE self)
{
- VALUE translations;
- i_object_t *root_object = ALLOC(i_object_t);
- root_object->type = i_type_hash;
- root_object->data.hash = NULL;
+ VALUE translations, key_cache;
+
+ i_object_t *root_object = new_hash_object();
translations = Data_Wrap_Struct(I18nemaBackend, 0, delete_object_r, root_object);
rb_iv_set(self, "@translations", translations);
+
+ i_object_t *key_map = new_hash_object();
+ key_cache = Data_Wrap_Struct(I18nemaBackend, 0, delete_object_r, key_map);
+ rb_iv_set(self, "@normalized_key_cache", key_cache);
+
return self;
}
void
Init_i18nema()
@@ -469,6 +584,7 @@
rb_define_method(I18nemaBackend, "initialize", initialize, 0);
rb_define_method(I18nemaBackend, "load_yml_string", load_yml_string, 1);
rb_define_method(I18nemaBackend, "available_locales", available_locales, 0);
rb_define_method(I18nemaBackend, "reload!", reload, 0);
rb_define_method(I18nemaBackend, "direct_lookup", direct_lookup, -1);
+ rb_define_method(I18nemaBackend, "normalize_key", normalize_key, 2);
}