i18nema 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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