i18nema 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,171 @@
1
+ /* yamlbyte.h
2
+ *
3
+ * The YAML bytecode "C" interface header file. See the YAML bytecode
4
+ * reference for bytecode sequence rules and for the meaning of each
5
+ * bytecode.
6
+ */
7
+
8
+ #ifndef YAMLBYTE_H
9
+ #define YAMLBYTE_H
10
+ #include <stddef.h>
11
+
12
+ /* define what a character is */
13
+ typedef unsigned char yamlbyte_utf8_t;
14
+ typedef unsigned short yamlbyte_utf16_t;
15
+ #ifdef YAMLBYTE_UTF8
16
+ #ifdef YAMLBYTE_UTF16
17
+ #error Must only define YAMLBYTE_UTF8 or YAMLBYTE_UTF16
18
+ #endif
19
+ typedef yamlbyte_utf8_t yamlbyte_char_t;
20
+ #else
21
+ #ifdef YAMLBYTE_UTF16
22
+ typedef yamlbyte_utf16_t yamlbyte_char_t;
23
+ #else
24
+ #error Must define YAMLBYTE_UTF8 or YAMLBYTE_UTF16
25
+ #endif
26
+ #endif
27
+
28
+ /* specify list of bytecodes */
29
+ #define YAMLBYTE_FINISH ((yamlbyte_char_t) 0)
30
+ #define YAMLBYTE_DOCUMENT ((yamlbyte_char_t)'D')
31
+ #define YAMLBYTE_DIRECTIVE ((yamlbyte_char_t)'V')
32
+ #define YAMLBYTE_PAUSE ((yamlbyte_char_t)'P')
33
+ #define YAMLBYTE_MAPPING ((yamlbyte_char_t)'M')
34
+ #define YAMLBYTE_SEQUENCE ((yamlbyte_char_t)'Q')
35
+ #define YAMLBYTE_END_BRANCH ((yamlbyte_char_t)'E')
36
+ #define YAMLBYTE_SCALAR ((yamlbyte_char_t)'S')
37
+ #define YAMLBYTE_CONTINUE ((yamlbyte_char_t)'C')
38
+ #define YAMLBYTE_NEWLINE ((yamlbyte_char_t)'N')
39
+ #define YAMLBYTE_NULLCHAR ((yamlbyte_char_t)'Z')
40
+ #define YAMLBYTE_ANCHOR ((yamlbyte_char_t)'A')
41
+ #define YAMLBYTE_ALIAS ((yamlbyte_char_t)'R')
42
+ #define YAMLBYTE_TRANSFER ((yamlbyte_char_t)'T')
43
+ /* formatting bytecodes */
44
+ #define YAMLBYTE_COMMENT ((yamlbyte_char_t)'c')
45
+ #define YAMLBYTE_INDENT ((yamlbyte_char_t)'i')
46
+ #define YAMLBYTE_STYLE ((yamlbyte_char_t)'s')
47
+ /* other bytecodes */
48
+ #define YAMLBYTE_LINE_NUMBER ((yamlbyte_char_t)'#')
49
+ #define YAMLBYTE_WHOLE_SCALAR ((yamlbyte_char_t)'<')
50
+ #define YAMLBYTE_NOTICE ((yamlbyte_char_t)'!')
51
+ #define YAMLBYTE_SPAN ((yamlbyte_char_t)')')
52
+ #define YAMLBYTE_ALLOC ((yamlbyte_char_t)'@')
53
+
54
+ /* second level style bytecodes, ie "s>" */
55
+ #define YAMLBYTE_FLOW ((yamlbyte_char_t)'>')
56
+ #define YAMLBYTE_LITERAL ((yamlbyte_char_t)'|')
57
+ #define YAMLBYTE_BLOCK ((yamlbyte_char_t)'b')
58
+ #define YAMLBYTE_PLAIN ((yamlbyte_char_t)'p')
59
+ #define YAMLBYTE_INLINE_MAPPING ((yamlbyte_char_t)'{')
60
+ #define YAMLBYTE_INLINE_SEQUENCE ((yamlbyte_char_t)'[')
61
+ #define YAMLBYTE_SINGLE_QUOTED ((yamlbyte_char_t)39)
62
+ #define YAMLBYTE_DOUBLE_QUOTED ((yamlbyte_char_t)'"')
63
+
64
+ /*
65
+ * The "C" API has two variants, one based on instructions,
66
+ * with events delivered via pointers; and the other one
67
+ * is character based where one or more instructions are
68
+ * serialized into a buffer.
69
+ *
70
+ * Note: In the instruction based API, WHOLE_SCALAR does
71
+ * not have the '<here' marshalling stuff.
72
+ */
73
+
74
+ typedef void * yamlbyte_consumer_t;
75
+ typedef void * yamlbyte_producer_t;
76
+
77
+ /* push and pull APIs need a way to communicate results */
78
+ typedef enum {
79
+ YAMLBYTE_OK = 0, /* proceed */
80
+ YAMLBYTE_E_MEMORY = 'M', /* could not allocate memory */
81
+ YAMLBYTE_E_READ = 'R', /* input stream read error */
82
+ YAMLBYTE_E_WRITE = 'W', /* output stream write error */
83
+ YAMLBYTE_E_OTHER = '?', /* some other error condition */
84
+ YAMLBYTE_E_PARSE = 'P', /* parse error, check bytecodes */
85
+ YAMLBYTE_MAX
86
+ } yamlbyte_result_t;
87
+
88
+ typedef const yamlbyte_char_t *yamlbyte_buff_t;
89
+
90
+ /*
91
+ * The "Instruction" API
92
+ */
93
+
94
+ typedef struct yaml_instruction {
95
+ yamlbyte_char_t bytecode;
96
+ yamlbyte_buff_t start;
97
+ yamlbyte_buff_t finish; /* open range, *finish is _not_ part */
98
+ } *yamlbyte_inst_t;
99
+
100
+ /* producer pushes the instruction with one bytecode event to the
101
+ * consumer; if the consumer's result is not YAMLBYTE_OK, then
102
+ * the producer should stop */
103
+ typedef
104
+ yamlbyte_result_t
105
+ (*yamlbyte_push_t)(
106
+ yamlbyte_consumer_t self,
107
+ yamlbyte_inst_t inst
108
+ );
109
+
110
+ /* consumer pulls a bytecode instruction from the producer; in this
111
+ * case the instruction (and is buffer) are owned by the producer and
112
+ * will remain valid till the pull function is called once again;
113
+ * if the instruction is NULL, then there are no more results; and
114
+ * it is important to call the pull function till it returns NULL so
115
+ * that the producer can clean up its memory allocations */
116
+ typedef
117
+ yamlbyte_result_t
118
+ (*yamlbyte_pull_t)(
119
+ yamlbyte_producer_t self,
120
+ yamlbyte_inst_t *inst /* to be filled in by the producer */
121
+ );
122
+
123
+ /*
124
+ * Buffer based API
125
+ */
126
+
127
+ /* producer pushes a null terminated buffer filled with one or more
128
+ * bytecode events to the consumer; if the consumer's result is not
129
+ * YAMLBYTE_OK, then the producer should stop */
130
+ typedef
131
+ yamlbyte_result_t
132
+ (*yamlbyte_pushbuff_t)(
133
+ yamlbyte_consumer_t self,
134
+ yamlbyte_buff_t buff
135
+ );
136
+
137
+ /* consumer pulls bytecode events from the producer; in this case
138
+ * the buffer is owned by the producer, and will remain valid till
139
+ * the pull function is called once again; if the buffer pointer
140
+ * is set to NULL, then there are no more results; it is important
141
+ * to call the pull function till it returns NULL so that the
142
+ * producer can clean up its memory allocations */
143
+ typedef
144
+ yamlbyte_result_t
145
+ (*yamlbyte_pullbuff_t)(
146
+ yamlbyte_producer_t self,
147
+ yamlbyte_buff_t *buff /* to be filled in by the producer */
148
+ );
149
+
150
+ /* convert a pull interface to a push interface; the reverse process
151
+ * requires threads and thus is language dependent */
152
+ #define YAMLBYTE_PULL2PUSH(pull,producer,push,consumer,result) \
153
+ do { \
154
+ yamlbyte_pullbuff_t _pull = (pull); \
155
+ yamlbyte_pushbuff_t _push = (push); \
156
+ yamlbyte_result_t _result = YAMLBYTE_OK; \
157
+ yamlbyte_producer_t _producer = (producer); \
158
+ yamlbyte_consumer_t _consumer = (consumer); \
159
+ while(1) { \
160
+ yamlbyte_buff_t buff = NULL; \
161
+ _result = _pull(_producer,&buff); \
162
+ if(YAMLBYTE_OK != result || NULL == buff) \
163
+ break; \
164
+ _result = _push(_consumer,buff); \
165
+ if(YAMLBYTE_OK != result) \
166
+ break; \
167
+ } \
168
+ (result) = _result; \
169
+ } while(0)
170
+
171
+ #endif
@@ -0,0 +1,8 @@
1
+ class Hash
2
+ def deep_stringify_keys
3
+ Hash[map{ |key, value|
4
+ value = value.deep_stringify_keys if value.is_a?(Hash)
5
+ [key.to_s, value]
6
+ }]
7
+ end unless Hash.method_defined?(:deep_stringify_keys)
8
+ end
data/lib/i18nema.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'syck'
2
+ require 'i18n'
3
+ require File.dirname(__FILE__) + '/i18nema/core_ext/hash'
4
+ require File.dirname(__FILE__) + '/i18nema/i18nema'
5
+
6
+ module I18nema
7
+ class Backend
8
+ include I18n::Backend::Base
9
+
10
+ def store_translations(locale, data, options = {})
11
+ # TODO: make this moar awesome
12
+ @initialized = true
13
+ load_yml_string({locale => data}.deep_stringify_keys.to_yaml)
14
+ end
15
+
16
+ def init_translations
17
+ load_translations
18
+ @initialized = true
19
+ end
20
+
21
+ protected
22
+ def load_file(filename)
23
+ type = File.extname(filename).tr('.', '').downcase
24
+ raise I18n::UnknownFileType.new(type, filename) unless type == "yml"
25
+ load_yml(filename)
26
+ end
27
+
28
+ def load_yml(filename)
29
+ load_yml_string File.read(filename)
30
+ end
31
+
32
+ def initialized?
33
+ @initialized
34
+ end
35
+
36
+ def lookup(locale, key, scope = [], options = {})
37
+ init_translations unless initialized?
38
+ keys = I18n.normalize_keys(locale, key, scope, options[:separator])
39
+ direct_lookup(*keys)
40
+ end
41
+ end
42
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/setup'
2
+ Bundler.require(:default)
3
+
4
+ require 'test/unit'
5
+ require 'i18nema'
@@ -0,0 +1,78 @@
1
+ require 'helper'
2
+ require 'yaml'
3
+
4
+ class I18nemaTest < Test::Unit::TestCase
5
+ def setup
6
+ @data = {
7
+ foo: {
8
+ bar: "lol"
9
+ },
10
+ baz: %w{
11
+ asdf
12
+ qwerty
13
+ }
14
+ }.deep_stringify_keys
15
+ @backend = I18nema::Backend.new
16
+ @backend.store_translations :en, @data
17
+ end
18
+
19
+ def test_yaml_parity
20
+ assert_equal({"en" => @data}, @backend.direct_lookup)
21
+ end
22
+
23
+ def test_scoping
24
+ assert_equal({"bar" => "lol"},
25
+ @backend.direct_lookup("en", "foo"))
26
+ assert_equal "lol",
27
+ @backend.direct_lookup("en", "foo", "bar")
28
+ assert_equal nil,
29
+ @backend.direct_lookup("poo")
30
+ end
31
+
32
+ def test_merging
33
+ @backend.store_translations :en, foo: "replaced!", wat: "added!"
34
+ assert_equal "replaced!",
35
+ @backend.direct_lookup("en", "foo")
36
+ assert_equal ["asdf", "qwerty"],
37
+ @backend.direct_lookup("en", "baz")
38
+ assert_equal "added!",
39
+ @backend.direct_lookup("en", "wat")
40
+ end
41
+
42
+ def test_reload
43
+ @backend.reload!
44
+ assert_equal({}, @backend.direct_lookup)
45
+ end
46
+
47
+ def test_available_locales
48
+ @backend.store_translations :es, foo: "hola"
49
+ assert_equal ['en', 'es'],
50
+ @backend.available_locales.map(&:to_s).sort
51
+ end
52
+
53
+ def test_invalid_yml
54
+ backend = I18nema::Backend.new
55
+
56
+ exception = assert_raise(I18nema::Backend::LoadError) {
57
+ backend.load_yml_string("string")
58
+ }
59
+ assert_equal("root yml node is not a hash", exception.message)
60
+ assert_equal({}, backend.direct_lookup)
61
+
62
+ # FIXME ... ruby syck does this differently, and we get a non
63
+ # i_object_t as the root node, causing delete_object to asplode when
64
+ # it tries to free a garbage pointer
65
+ #
66
+ #exception = assert_raise(I18nema::Backend::LoadError) {
67
+ # backend.load_yml_string("en:\n foo: \"lol\"\n\tbar: notabs!")
68
+ #}
69
+ #assert_match(/TAB found in your indentation/, exception.message)
70
+ #assert_equal({}, backend.direct_lookup)
71
+
72
+ exception = assert_raise(I18nema::Backend::LoadError) {
73
+ backend.load_yml_string("en:\n &a [*a]")
74
+ }
75
+ assert_match(/bad anchor `a'/, exception.message)
76
+ assert_equal({}, backend.direct_lookup)
77
+ end
78
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: i18nema
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jon Jensen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: i18n
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0.5'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0.5'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake-compiler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0.8'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0.8'
46
+ description: drop-in replacement for I18n::Backend::Simple for faster lookups and
47
+ quicker gc runs. translations are stored outside of the ruby heap
48
+ email: jon@instructure.com.com
49
+ executables: []
50
+ extensions:
51
+ - ext/i18nema/extconf.rb
52
+ - ext/i18nema/mkrf_conf.rb
53
+ extra_rdoc_files: []
54
+ files:
55
+ - Rakefile
56
+ - README.md
57
+ - ext/i18nema/i18nema.c
58
+ - ext/i18nema/extconf.h
59
+ - ext/i18nema/gram.h
60
+ - ext/i18nema/syck.h
61
+ - ext/i18nema/uthash.h
62
+ - ext/i18nema/yamlbyte.h
63
+ - ext/i18nema/extconf.rb
64
+ - ext/i18nema/mkrf_conf.rb
65
+ - lib/i18nema/core_ext/hash.rb
66
+ - lib/i18nema.rb
67
+ - test/helper.rb
68
+ - test/i18nema_test.rb
69
+ homepage: http://github.com/instructure/i18nema
70
+ licenses: []
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: 1.9.3
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: 1.3.5
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.25
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: fast i18n backend that doesn't stop up the garbage collector
93
+ test_files: []
94
+ has_rdoc: