i18nema 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # I18nema
2
+
3
+ Fast I18n backend to keep things running smoothly.
4
+
5
+ I18nema is a drop-in replacement for I18n::Backend::Simple, for faster
6
+ lookups and quicker gc runs. Translations are stored outside of the ruby
7
+ heap, and lookups happen in C (rather than the usual inject on nested
8
+ ruby hashes).
9
+
10
+ ## How do I use it?
11
+
12
+ gem 'i18nema'
13
+
14
+ and then do something like this in an initializer:
15
+
16
+ I18n.backend = I18nema::Backend.new
17
+
18
+ As with I18n::Backend::Simple, you can pull in additional features, e.g.
19
+
20
+ I18nema::Backend.send(:include, I18n::Backend::Fallbacks)
21
+
22
+ ## Notes
23
+
24
+ You should probably make sure translations are loaded before you fork. In
25
+ an initializer, just do `I18n.backend.init_translations`
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'rake'
2
+ require 'rake/extensiontask'
3
+
4
+ desc 'Default: run unit tests.'
5
+ task :default => :test
6
+
7
+ require 'rake/testtask'
8
+ desc 'Test the immigrant plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+ Rake::Task[:test].prerequisites << :compile
16
+
17
+ Rake::ExtensionTask.new('i18nema') do |ext|
18
+ ext.lib_dir = File.join('lib', 'i18nema')
19
+ end
@@ -0,0 +1,3 @@
1
+ #ifndef EXTCONF_H
2
+ #define EXTCONF_H
3
+ #endif
@@ -0,0 +1,5 @@
1
+ require 'mkmf'
2
+ dir_config "i18nema/i18nema"
3
+ have_header "st.h"
4
+ $CFLAGS << " -std=c99"
5
+ create_makefile 'i18nema/i18nema'
@@ -0,0 +1,79 @@
1
+ /* A Bison parser, made by GNU Bison 1.875d. */
2
+
3
+ /* Skeleton parser for Yacc-like parsing with Bison,
4
+ Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
5
+
6
+ This program is free software; you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation; either version 2, or (at your option)
9
+ any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program; if not, write to the Free Software
18
+ Foundation, Inc., 59 Temple Place - Suite 330,
19
+ Boston, MA 02111-1307, USA. */
20
+
21
+ /* As a special exception, when this file is copied by Bison into a
22
+ Bison output file, you may use that output file without restriction.
23
+ This special exception was added by the Free Software Foundation
24
+ in version 1.24 of Bison. */
25
+
26
+ /* Tokens. */
27
+ #ifndef YYTOKENTYPE
28
+ # define YYTOKENTYPE
29
+ /* Put the tokens into the symbol table, so that GDB and other debuggers
30
+ know about them. */
31
+ enum yytokentype {
32
+ YAML_ANCHOR = 258,
33
+ YAML_ALIAS = 259,
34
+ YAML_TRANSFER = 260,
35
+ YAML_TAGURI = 261,
36
+ YAML_ITRANSFER = 262,
37
+ YAML_WORD = 263,
38
+ YAML_PLAIN = 264,
39
+ YAML_BLOCK = 265,
40
+ YAML_DOCSEP = 266,
41
+ YAML_IOPEN = 267,
42
+ YAML_INDENT = 268,
43
+ YAML_IEND = 269
44
+ };
45
+ #endif
46
+ #define YAML_ANCHOR 258
47
+ #define YAML_ALIAS 259
48
+ #define YAML_TRANSFER 260
49
+ #define YAML_TAGURI 261
50
+ #define YAML_ITRANSFER 262
51
+ #define YAML_WORD 263
52
+ #define YAML_PLAIN 264
53
+ #define YAML_BLOCK 265
54
+ #define YAML_DOCSEP 266
55
+ #define YAML_IOPEN 267
56
+ #define YAML_INDENT 268
57
+ #define YAML_IEND 269
58
+
59
+
60
+
61
+
62
+ #if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED)
63
+ #line 35 "gram.y"
64
+ typedef union YYSTYPE {
65
+ SYMID nodeId;
66
+ SyckNode *nodeData;
67
+ char *name;
68
+ } YYSTYPE;
69
+ /* Line 1285 of yacc.c. */
70
+ #line 71 "gram.h"
71
+ # define yystype YYSTYPE /* obsolescent; will be withdrawn */
72
+ # define YYSTYPE_IS_DECLARED 1
73
+ # define YYSTYPE_IS_TRIVIAL 1
74
+ #endif
75
+
76
+
77
+
78
+
79
+
@@ -0,0 +1,397 @@
1
+ #include <ruby.h>
2
+ #include <syck.h>
3
+ #include "uthash.h"
4
+
5
+ VALUE I18nema = Qnil,
6
+ I18nemaBackend = Qnil,
7
+ I18nemaBackendLoadError = Qnil;
8
+
9
+ struct i_object;
10
+ struct i_key_value;
11
+ static VALUE array_to_rarray(struct i_object *array);
12
+ static VALUE hash_to_rhash(struct i_object *hash);
13
+ static void merge_hash(struct i_object *hash, struct i_object *other_hash);
14
+ static void delete_hash(struct i_key_value **hash);
15
+ static void delete_object(struct i_object *object);
16
+
17
+ enum i_object_type {
18
+ i_type_string,
19
+ i_type_array,
20
+ i_type_hash
21
+ };
22
+
23
+ union i_object_data {
24
+ char *string;
25
+ struct i_object **array;
26
+ struct i_key_value *hash;
27
+ };
28
+
29
+ typedef struct i_object
30
+ {
31
+ unsigned long size;
32
+ enum i_object_type type;
33
+ union i_object_data data;
34
+ } i_object_t;
35
+
36
+ typedef struct i_key_value
37
+ {
38
+ char *key;
39
+ struct i_object *value;
40
+ UT_hash_handle hh;
41
+ } i_key_value_t;
42
+
43
+ static int current_translation_count = 0;
44
+ static ID s_to_s,
45
+ s_init_translations;
46
+
47
+ static VALUE
48
+ i_object_to_robject(i_object_t *object) {
49
+ if (object == NULL)
50
+ return Qnil;
51
+ switch (object->type) {
52
+ case i_type_string:
53
+ return rb_str_new2(object->data.string);
54
+ case i_type_array:
55
+ return array_to_rarray(object);
56
+ case i_type_hash:
57
+ return hash_to_rhash(object);
58
+ }
59
+ return Qnil;
60
+ }
61
+
62
+ static VALUE
63
+ array_to_rarray(i_object_t *array)
64
+ {
65
+ VALUE result = rb_ary_new2(array->size);
66
+ for (unsigned long i = 0; i < array->size; i++)
67
+ rb_ary_store(result, i, i_object_to_robject(array->data.array[i]));
68
+ return result;
69
+ }
70
+
71
+ static VALUE
72
+ hash_to_rhash(i_object_t *hash)
73
+ {
74
+ i_key_value_t *handle = hash->data.hash;
75
+ VALUE result = rb_hash_new();
76
+ for (; handle != NULL; handle = handle->hh.next)
77
+ rb_hash_aset(result, rb_str_new2(handle->key), i_object_to_robject(handle->value));
78
+ return result;
79
+ }
80
+
81
+ static i_object_t*
82
+ root_object_get(VALUE self)
83
+ {
84
+ i_object_t *root_object;
85
+ VALUE translations;
86
+ translations = rb_iv_get(self, "@translations");
87
+ Data_Get_Struct(translations, i_object_t, root_object);
88
+ return root_object;
89
+ }
90
+
91
+ /*
92
+ * call-seq:
93
+ * backend.direct_lookup([part]+) -> localized_str
94
+ *
95
+ * Returns the translation(s) found under the specified key.
96
+ *
97
+ * backend.direct_lookup("en", "foo", "bar") #=> "lol"
98
+ * backend.direct_lookup("en", "foo") #=> {"bar"=>"lol", "baz"=>["asdf", "qwerty"]}
99
+ */
100
+
101
+ static VALUE
102
+ direct_lookup(int argc, VALUE *argv, VALUE self)
103
+ {
104
+ i_object_t *result = root_object_get(self);;
105
+ i_key_value_t *kv = NULL;
106
+ VALUE rs;
107
+ char *s;
108
+
109
+ for (int i = 0; i < argc && result != NULL && result->type == i_type_hash; i++) {
110
+ rs = rb_funcall(argv[i], s_to_s, 0);
111
+ s = StringValueCStr(rs);
112
+ HASH_FIND_STR(result->data.hash, s, kv);
113
+ result = kv == NULL ? NULL : kv->value;
114
+ }
115
+
116
+ return i_object_to_robject(result);
117
+ }
118
+
119
+ static void
120
+ empty_object(i_object_t *object)
121
+ {
122
+ if (object == NULL)
123
+ return;
124
+
125
+ switch (object->type) {
126
+ case i_type_string:
127
+ xfree(object->data.string);
128
+ break;
129
+ case i_type_array:
130
+ for (unsigned long i = 0; i < object->size; i++)
131
+ delete_object(object->data.array[i]);
132
+ xfree(object->data.array);
133
+ break;
134
+ case i_type_hash:
135
+ delete_hash(&object->data.hash);
136
+ break;
137
+ }
138
+ }
139
+
140
+ static void
141
+ delete_object(i_object_t *object)
142
+ {
143
+ empty_object(object);
144
+ xfree(object);
145
+ }
146
+
147
+ static void
148
+ delete_key_value(i_key_value_t *kv, int delete_value)
149
+ {
150
+ if (delete_value)
151
+ delete_object(kv->value);
152
+ xfree(kv->key);
153
+ xfree(kv);
154
+ }
155
+
156
+ static void
157
+ delete_hash(i_key_value_t **hash)
158
+ {
159
+ i_key_value_t *kv, *tmp;
160
+ HASH_ITER(hh, *hash, kv, tmp) {
161
+ HASH_DEL(*hash, kv);
162
+ delete_key_value(kv, 1);
163
+ }
164
+ }
165
+
166
+ static void
167
+ add_key_value(i_key_value_t **hash, i_key_value_t *kv)
168
+ {
169
+ i_key_value_t *existing = NULL;
170
+ HASH_FIND_STR(*hash, kv->key, existing);
171
+
172
+ if (existing != NULL) {
173
+ if (existing->value->type == i_type_hash && kv->value->type == i_type_hash) {
174
+ merge_hash(existing->value, kv->value);
175
+ delete_key_value(kv, 0);
176
+ return;
177
+ }
178
+ HASH_DEL(*hash, existing);
179
+ delete_key_value(existing, 1);
180
+ }
181
+ HASH_ADD_KEYPTR(hh, *hash, kv->key, strlen(kv->key), kv);
182
+ }
183
+
184
+ static void
185
+ merge_hash(i_object_t *hash, i_object_t *other_hash)
186
+ {
187
+ i_key_value_t *kv, *tmp;
188
+
189
+ HASH_ITER(hh, other_hash->data.hash, kv, tmp) {
190
+ HASH_DEL(other_hash->data.hash, kv);
191
+ add_key_value(&hash->data.hash, kv);
192
+ }
193
+ delete_object(other_hash);
194
+ }
195
+
196
+ static int
197
+ delete_syck_st_entry(char *key, char *value, char *arg)
198
+ {
199
+ return ST_DELETE;
200
+ }
201
+
202
+ static int
203
+ delete_syck_object(char *key, char *value, char *arg)
204
+ {
205
+ i_object_t *object = (i_object_t *)value;
206
+ delete_object(object);
207
+ return ST_DELETE;
208
+ }
209
+
210
+ static void
211
+ handle_syck_error(SyckParser *parser, const char *str)
212
+ {
213
+ char *endl = parser->cursor;
214
+ while (*endl != '\0' && *endl != '\n')
215
+ endl++;
216
+ endl[0] = '\0';
217
+
218
+ if (parser->syms)
219
+ st_foreach(parser->syms, delete_syck_object, 0);
220
+ rb_raise(I18nemaBackendLoadError, "%s on line %d, col %ld: `%s'", str, parser->linect + 1, parser->cursor - parser->lineptr, parser->lineptr);
221
+ }
222
+
223
+ static SyckNode*
224
+ handle_syck_badanchor(SyckParser *parser, char *anchor)
225
+ {
226
+ char error[strlen(anchor) + 14];
227
+ sprintf(error, "bad anchor `%s'", anchor);
228
+ handle_syck_error(parser, error);
229
+ return NULL;
230
+ }
231
+
232
+ static SYMID
233
+ handle_syck_node(SyckParser *parser, SyckNode *node)
234
+ {
235
+ i_object_t *result;
236
+ result = ALLOC(i_object_t);
237
+ SYMID oid;
238
+
239
+ switch (node->kind) {
240
+ case syck_str_kind:
241
+ // TODO: why does syck sometimes give us empty string nodes? (small) memory leak, since they never end up in a seq/map
242
+ result->type = i_type_string;
243
+ result->size = node->data.str->len;
244
+ result->data.string = xmalloc(node->data.str->len + 1);
245
+ strncpy(result->data.string, node->data.str->ptr, node->data.str->len);
246
+ result->data.string[node->data.str->len] = '\0';
247
+ break;
248
+ case syck_seq_kind:
249
+ result->type = i_type_array;
250
+ result->size = node->data.list->idx;
251
+ result->data.array = ALLOC_N(i_object_t*, node->data.list->idx);
252
+ for (long i = 0; i < node->data.list->idx; i++) {
253
+ i_object_t *item = NULL;
254
+
255
+ oid = syck_seq_read(node, i);
256
+ syck_lookup_sym(parser, oid, (void **)&item);
257
+ if (item->type == i_type_string)
258
+ current_translation_count++;
259
+ result->data.array[i] = item;
260
+ }
261
+ break;
262
+ case syck_map_kind:
263
+ result->type = i_type_hash;
264
+ result->data.hash = NULL;
265
+ for (long i = 0; i < node->data.pairs->idx; i++) {
266
+ i_object_t *key = NULL, *value = NULL;
267
+
268
+ oid = syck_map_read(node, map_key, i);
269
+ syck_lookup_sym(parser, oid, (void **)&key);
270
+ oid = syck_map_read(node, map_value, i);
271
+ syck_lookup_sym(parser, oid, (void **)&value);
272
+
273
+ i_key_value_t *kv;
274
+ kv = ALLOC(i_key_value_t);
275
+ kv->key = key->data.string;
276
+ xfree(key);
277
+ kv->value = value;
278
+ if (value->type == i_type_string)
279
+ current_translation_count++;
280
+ add_key_value(&result->data.hash, kv);
281
+ }
282
+ break;
283
+ }
284
+
285
+
286
+ return syck_add_sym(parser, (char *)result);
287
+ }
288
+
289
+ /*
290
+ * call-seq:
291
+ * backend.load_yaml_string(yaml_str) -> num_translations
292
+ *
293
+ * Loads translations from the specified yaml string, and returns the
294
+ * number of (new) translations stored.
295
+ *
296
+ * backend.load_yaml_string("en:\n foo: bar") #=> 1
297
+ */
298
+
299
+ static VALUE
300
+ load_yml_string(VALUE self, VALUE yml)
301
+ {
302
+ SYMID oid;
303
+ i_object_t *root_object = root_object_get(self);
304
+ i_object_t *new_root_object = NULL;
305
+ current_translation_count = 0;
306
+ SyckParser* parser = syck_new_parser();
307
+ syck_parser_handler(parser, handle_syck_node);
308
+ StringValue(yml);
309
+ syck_parser_str(parser, RSTRING_PTR(yml), RSTRING_LEN(yml), NULL);
310
+ syck_parser_bad_anchor_handler(parser, handle_syck_badanchor);
311
+ syck_parser_error_handler(parser, handle_syck_error);
312
+
313
+ oid = syck_parse(parser);
314
+ syck_lookup_sym(parser, oid, (void **)&new_root_object);
315
+ if (parser->syms)
316
+ st_foreach(parser->syms, delete_syck_st_entry, 0);
317
+ syck_free_parser(parser);
318
+ if (new_root_object == NULL || new_root_object->type != i_type_hash) {
319
+ delete_object(new_root_object);
320
+ rb_raise(I18nemaBackendLoadError, "root yml node is not a hash");
321
+ }
322
+ merge_hash(root_object, new_root_object);
323
+
324
+ return INT2NUM(current_translation_count);
325
+ }
326
+
327
+ /*
328
+ * call-seq:
329
+ * backend.available_locales -> locales
330
+ *
331
+ * Returns the currently loaded locales. Order is not guaranteed.
332
+ *
333
+ * backend.available_locales #=> [:en, :es]
334
+ */
335
+
336
+ static VALUE
337
+ available_locales(VALUE self)
338
+ {
339
+ if (!RTEST(rb_iv_get(self, "@initialized")))
340
+ rb_funcall(self, s_init_translations, 0);
341
+ i_object_t *root_object = root_object_get(self);
342
+ i_key_value_t *current = root_object->data.hash;
343
+ VALUE ary = rb_ary_new2(0);
344
+
345
+ for (; current != NULL; current = current->hh.next)
346
+ rb_ary_push(ary, rb_str_intern(rb_str_new2(current->key)));
347
+
348
+ return ary;
349
+ }
350
+
351
+ /*
352
+ * call-seq:
353
+ * backend.reload! -> true
354
+ *
355
+ * Clears out all currently stored translations.
356
+ *
357
+ * backend.reload! #=> true
358
+ */
359
+
360
+ static VALUE
361
+ reload(VALUE self)
362
+ {
363
+ i_object_t *root_object = root_object_get(self);
364
+ empty_object(root_object);
365
+ rb_iv_set(self, "@initialized", Qfalse);
366
+ root_object = NULL;
367
+ return Qtrue;
368
+ }
369
+
370
+ static VALUE
371
+ initialize(VALUE self)
372
+ {
373
+ VALUE translations;
374
+ i_object_t *root_object = ALLOC(i_object_t);
375
+ root_object->type = i_type_hash;
376
+ root_object->data.hash = NULL;
377
+ translations = Data_Wrap_Struct(I18nemaBackend, 0, delete_object, root_object);
378
+ rb_iv_set(self, "@translations", translations);
379
+ return self;
380
+ }
381
+
382
+ void
383
+ Init_i18nema()
384
+ {
385
+ I18nema = rb_define_module("I18nema");
386
+ I18nemaBackend = rb_define_class_under(I18nema, "Backend", rb_cObject);
387
+ I18nemaBackendLoadError = rb_define_class_under(I18nemaBackend, "LoadError", rb_eStandardError);
388
+
389
+ s_to_s = rb_intern("to_s");
390
+ s_init_translations = rb_intern("init_translations");
391
+
392
+ rb_define_method(I18nemaBackend, "initialize", initialize, 0);
393
+ rb_define_method(I18nemaBackend, "load_yml_string", load_yml_string, 1);
394
+ rb_define_method(I18nemaBackend, "available_locales", available_locales, 0);
395
+ rb_define_method(I18nemaBackend, "reload!", reload, 0);
396
+ rb_define_method(I18nemaBackend, "direct_lookup", direct_lookup, -1);
397
+ }
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'rubygems/command.rb'
3
+ require 'rubygems/dependency_installer.rb'
4
+ begin
5
+ Gem::Command.build_args = ARGV
6
+ rescue NoMethodError
7
+ end
8
+ inst = Gem::DependencyInstaller.new
9
+ begin
10
+ if RUBY_VERSION >= "2.0"
11
+ inst.install "syck", "1.0.0"
12
+ end
13
+ rescue
14
+ exit(1)
15
+ end
16
+
17
+ f = File.open(File.join(File.dirname(__FILE__), "Rakefile"), "w") # create dummy rakefile to indicate success
18
+ f.write("task :default\n")
19
+ f.close