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 +25 -0
- data/Rakefile +19 -0
- data/ext/i18nema/extconf.h +3 -0
- data/ext/i18nema/extconf.rb +5 -0
- data/ext/i18nema/gram.h +79 -0
- data/ext/i18nema/i18nema.c +397 -0
- data/ext/i18nema/mkrf_conf.rb +19 -0
- data/ext/i18nema/syck.h +453 -0
- data/ext/i18nema/uthash.h +940 -0
- data/ext/i18nema/yamlbyte.h +171 -0
- data/lib/i18nema/core_ext/hash.rb +8 -0
- data/lib/i18nema.rb +42 -0
- data/test/helper.rb +5 -0
- data/test/i18nema_test.rb +78 -0
- metadata +94 -0
@@ -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
|
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,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:
|