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); }