twitter_cldr 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. data/Gemfile +32 -0
  2. data/History.txt +78 -0
  3. data/README.md +72 -62
  4. data/Rakefile +22 -0
  5. data/js/lib/compiler.rb +40 -0
  6. data/js/lib/mustache/bundle.coffee +14 -0
  7. data/js/lib/mustache/calendars/datetime.coffee +240 -0
  8. data/js/lib/mustache/calendars/timespan.coffee +52 -0
  9. data/js/lib/mustache/plurals/rules.coffee +14 -0
  10. data/js/lib/renderers/base.rb +18 -0
  11. data/js/lib/renderers/bundle.rb +18 -0
  12. data/js/lib/renderers/calendars/datetime_renderer.rb +34 -0
  13. data/js/lib/renderers/calendars/timespan_renderer.rb +39 -0
  14. data/js/lib/renderers/plurals/rules/plural_rules_compiler.rb +89 -0
  15. data/js/lib/renderers/plurals/rules/plural_rules_renderer.rb +26 -0
  16. data/js/lib/twitter_cldr_js.rb +85 -0
  17. data/js/spec/js/calendars/datetime_spec.js +418 -0
  18. data/js/spec/js/calendars/timespan_spec.js +91 -0
  19. data/js/spec/js/plurals/plural_rules_spec.js +28 -0
  20. data/js/spec/js/support/jasmine.yml +8 -0
  21. data/js/spec/rb/renderers/plurals/plural_rules_compiler_spec.rb +52 -0
  22. data/js/spec/rb/spec_helper.rb +13 -0
  23. data/lib/twitter_cldr.rb +2 -1
  24. data/lib/twitter_cldr/collation.rb +2 -1
  25. data/lib/twitter_cldr/collation/collator.rb +49 -31
  26. data/lib/twitter_cldr/collation/{sort_key.rb → sort_key_builder.rb} +31 -8
  27. data/lib/twitter_cldr/collation/trie.rb +116 -24
  28. data/lib/twitter_cldr/collation/trie_builder.rb +54 -28
  29. data/lib/twitter_cldr/collation/trie_with_fallback.rb +55 -0
  30. data/lib/twitter_cldr/core_ext/array.rb +14 -1
  31. data/lib/twitter_cldr/core_ext/calendars/datetime.rb +8 -2
  32. data/lib/twitter_cldr/core_ext/calendars/timespan.rb +5 -5
  33. data/lib/twitter_cldr/formatters/calendars/timespan_formatter.rb +10 -10
  34. data/lib/twitter_cldr/formatters/plurals/rules.rb +3 -5
  35. data/lib/twitter_cldr/resources.rb +11 -0
  36. data/lib/twitter_cldr/resources/import.rb +12 -0
  37. data/lib/twitter_cldr/resources/import/tailoring.rb +193 -0
  38. data/lib/twitter_cldr/{shared/resources.rb → resources/loader.rb} +17 -4
  39. data/lib/twitter_cldr/shared.rb +0 -1
  40. data/lib/twitter_cldr/tokenizers/base.rb +9 -9
  41. data/lib/twitter_cldr/tokenizers/calendars/datetime_tokenizer.rb +0 -4
  42. data/lib/twitter_cldr/tokenizers/calendars/timespan_tokenizer.rb +21 -7
  43. data/lib/twitter_cldr/utils.rb +11 -0
  44. data/lib/twitter_cldr/version.rb +1 -1
  45. data/resources/collation/tailoring/af.yml +3 -0
  46. data/resources/collation/tailoring/ar.yml +21 -0
  47. data/resources/collation/tailoring/ca.yml +9 -0
  48. data/resources/collation/tailoring/cs.yml +25 -0
  49. data/resources/collation/tailoring/da.yml +59 -0
  50. data/resources/collation/tailoring/de.yml +3 -0
  51. data/resources/collation/tailoring/el.yml +3 -0
  52. data/resources/collation/tailoring/en.yml +3 -0
  53. data/resources/collation/tailoring/es.yml +5 -0
  54. data/resources/collation/tailoring/eu.yml +3 -0
  55. data/resources/collation/tailoring/fa.yml +73 -0
  56. data/resources/collation/tailoring/fi.yml +61 -0
  57. data/resources/collation/tailoring/fil.yml +11 -0
  58. data/resources/collation/tailoring/fr.yml +3 -0
  59. data/resources/collation/tailoring/he.yml +3 -0
  60. data/resources/collation/tailoring/hi.yml +7 -0
  61. data/resources/collation/tailoring/hu.yml +125 -0
  62. data/resources/collation/tailoring/id.yml +3 -0
  63. data/resources/collation/tailoring/it.yml +3 -0
  64. data/resources/collation/tailoring/ja.yml +14647 -0
  65. data/resources/collation/tailoring/ko.yml +14953 -0
  66. data/resources/collation/tailoring/ms.yml +3 -0
  67. data/resources/collation/tailoring/nb.yml +59 -0
  68. data/resources/collation/tailoring/nl.yml +3 -0
  69. data/resources/collation/tailoring/pl.yml +37 -0
  70. data/resources/collation/tailoring/pt.yml +3 -0
  71. data/resources/collation/tailoring/ru.yml +3 -0
  72. data/resources/collation/tailoring/sv.yml +63 -0
  73. data/resources/collation/tailoring/th.yml +19 -0
  74. data/resources/collation/tailoring/tr.yml +27 -0
  75. data/resources/collation/tailoring/uk.yml +5 -0
  76. data/resources/collation/tailoring/ur.yml +163 -0
  77. data/resources/collation/tailoring/zh-Hant.yml +3 -0
  78. data/resources/collation/tailoring/zh.yml +149 -0
  79. data/resources/custom/locales/af/units.yml +19 -0
  80. data/resources/custom/locales/ar/units.yml +35 -0
  81. data/resources/custom/locales/ca/units.yml +19 -0
  82. data/resources/custom/locales/cs/units.yml +23 -0
  83. data/resources/custom/locales/da/units.yml +19 -0
  84. data/resources/custom/locales/de/units.yml +19 -0
  85. data/resources/custom/locales/el/units.yml +19 -0
  86. data/resources/custom/locales/en/units.yml +18 -0
  87. data/resources/custom/locales/es/units.yml +19 -0
  88. data/resources/custom/locales/eu/units.yml +19 -0
  89. data/resources/custom/locales/fa/units.yml +15 -0
  90. data/resources/custom/locales/fi/units.yml +19 -0
  91. data/resources/custom/locales/fil/units.yml +19 -0
  92. data/resources/custom/locales/fr/units.yml +19 -0
  93. data/resources/custom/locales/he/units.yml +19 -0
  94. data/resources/custom/locales/hi/units.yml +19 -0
  95. data/resources/custom/locales/hu/units.yml +15 -0
  96. data/resources/custom/locales/id/units.yml +15 -0
  97. data/resources/custom/locales/it/units.yml +19 -0
  98. data/resources/custom/locales/ja/units.yml +15 -0
  99. data/resources/custom/locales/ko/units.yml +15 -0
  100. data/resources/custom/locales/ms/units.yml +15 -0
  101. data/resources/custom/locales/nb/units.yml +19 -0
  102. data/resources/custom/locales/nl/units.yml +19 -0
  103. data/resources/custom/locales/pl/units.yml +23 -0
  104. data/resources/custom/locales/pt/units.yml +19 -0
  105. data/resources/custom/locales/ru/units.yml +27 -0
  106. data/resources/custom/locales/sv/units.yml +19 -0
  107. data/resources/custom/locales/th/units.yml +15 -0
  108. data/resources/custom/locales/tr/units.yml +15 -0
  109. data/resources/custom/locales/uk/units.yml +27 -0
  110. data/resources/custom/locales/ur/units.yml +19 -0
  111. data/resources/custom/locales/zh-Hant/units.yml +15 -0
  112. data/resources/custom/locales/zh/units.yml +15 -0
  113. data/resources/locales/af/units.yml +112 -65
  114. data/resources/locales/ar/units.yml +196 -126
  115. data/resources/locales/ca/units.yml +112 -70
  116. data/resources/locales/cs/units.yml +140 -91
  117. data/resources/locales/da/units.yml +98 -56
  118. data/resources/locales/de/units.yml +112 -70
  119. data/resources/locales/el/units.yml +119 -84
  120. data/resources/locales/en/units.yml +84 -42
  121. data/resources/locales/es/units.yml +112 -70
  122. data/resources/locales/eu/units.yml +105 -68
  123. data/resources/locales/fa/units.yml +98 -63
  124. data/resources/locales/fi/units.yml +112 -70
  125. data/resources/locales/fil/units.yml +98 -56
  126. data/resources/locales/fr/units.yml +112 -70
  127. data/resources/locales/he/units.yml +98 -56
  128. data/resources/locales/hi/units.yml +98 -56
  129. data/resources/locales/hu/units.yml +84 -49
  130. data/resources/locales/id/units.yml +84 -49
  131. data/resources/locales/it/units.yml +98 -56
  132. data/resources/locales/ja/units.yml +84 -49
  133. data/resources/locales/ko/units.yml +84 -49
  134. data/resources/locales/ms/units.yml +112 -63
  135. data/resources/locales/nb/units.yml +106 -64
  136. data/resources/locales/nl/units.yml +98 -56
  137. data/resources/locales/pl/units.yml +181 -112
  138. data/resources/locales/pt/units.yml +112 -70
  139. data/resources/locales/ru/units.yml +168 -112
  140. data/resources/locales/sv/units.yml +112 -70
  141. data/resources/locales/th/units.yml +84 -49
  142. data/resources/locales/tr/units.yml +84 -49
  143. data/resources/locales/uk/units.yml +168 -112
  144. data/resources/locales/ur/units.yml +112 -63
  145. data/resources/locales/zh-Hant/units.yml +84 -49
  146. data/resources/locales/zh/units.yml +84 -49
  147. data/spec/collation/collation_spec.rb +1 -1
  148. data/spec/collation/collator_spec.rb +120 -48
  149. data/spec/collation/sort_key_builder_spec.rb +80 -0
  150. data/spec/collation/tailoring_spec.rb +137 -0
  151. data/spec/collation/tailoring_tests/af.txt +321 -0
  152. data/spec/collation/tailoring_tests/ar.txt +188 -0
  153. data/spec/collation/tailoring_tests/ca.txt +446 -0
  154. data/spec/collation/tailoring_tests/cs.txt +273 -0
  155. data/spec/collation/tailoring_tests/da.txt +293 -0
  156. data/spec/collation/tailoring_tests/de.txt +414 -0
  157. data/spec/collation/tailoring_tests/el.txt +228 -0
  158. data/spec/collation/tailoring_tests/en.txt +399 -0
  159. data/spec/collation/tailoring_tests/es.txt +402 -0
  160. data/spec/collation/tailoring_tests/eu.txt +183 -0
  161. data/spec/collation/tailoring_tests/fa.txt +263 -0
  162. data/spec/collation/tailoring_tests/fi.txt +389 -0
  163. data/spec/collation/tailoring_tests/fil.txt +279 -0
  164. data/spec/collation/tailoring_tests/fr.txt +363 -0
  165. data/spec/collation/tailoring_tests/he.txt +167 -0
  166. data/spec/collation/tailoring_tests/hi.txt +230 -0
  167. data/spec/collation/tailoring_tests/hu.txt +773 -0
  168. data/spec/collation/tailoring_tests/id.txt +171 -0
  169. data/spec/collation/tailoring_tests/it.txt +231 -0
  170. data/spec/collation/tailoring_tests/ja.txt +4287 -0
  171. data/spec/collation/tailoring_tests/ko.txt +1761 -0
  172. data/spec/collation/tailoring_tests/ms.txt +531 -0
  173. data/spec/collation/tailoring_tests/nb.txt +375 -0
  174. data/spec/collation/tailoring_tests/nl.txt +273 -0
  175. data/spec/collation/tailoring_tests/pl.txt +225 -0
  176. data/spec/collation/tailoring_tests/pt.txt +405 -0
  177. data/spec/collation/tailoring_tests/ru.txt +213 -0
  178. data/spec/collation/tailoring_tests/sv.txt +353 -0
  179. data/spec/collation/tailoring_tests/th.txt +239 -0
  180. data/spec/collation/tailoring_tests/tr.txt +414 -0
  181. data/spec/collation/tailoring_tests/uk.txt +218 -0
  182. data/spec/collation/tailoring_tests/ur.txt +284 -0
  183. data/spec/collation/tailoring_tests/zh-Hant.txt +626 -0
  184. data/spec/collation/tailoring_tests/zh.txt +717 -0
  185. data/spec/collation/trie_builder_spec.rb +131 -51
  186. data/spec/collation/trie_spec.rb +301 -26
  187. data/spec/collation/trie_with_fallback_spec.rb +41 -0
  188. data/spec/core_ext/array_spec.rb +46 -3
  189. data/spec/core_ext/calendars/date_spec.rb +24 -24
  190. data/spec/core_ext/calendars/datetime_spec.rb +7 -0
  191. data/spec/core_ext/calendars/time_spec.rb +2 -2
  192. data/spec/formatters/calendars/timespan_formatter_spec.rb +47 -18
  193. data/spec/formatters/plurals/rules_spec.rb +3 -11
  194. data/spec/readme_spec.rb +15 -15
  195. data/spec/resources/loader_spec.rb +94 -0
  196. data/spec/spec_helper.rb +6 -0
  197. data/spec/tokenizers/calendars/timespan_tokenizer_spec.rb +1 -1
  198. data/spec/twitter_cldr_spec.rb +3 -3
  199. data/spec/utils_spec.rb +38 -0
  200. data/twitter_cldr.gemspec +25 -0
  201. metadata +156 -110
  202. data/spec/collation/sort_key_spec.rb +0 -56
  203. data/spec/shared/resources_spec.rb +0 -75
@@ -9,41 +9,28 @@ include TwitterCldr::Collation
9
9
 
10
10
  describe TrieBuilder do
11
11
 
12
- describe '#build' do
13
- describe 'fractional CE trie hash' do
14
- let(:trie_builder) do
15
- builder = TrieBuilder.new('resource')
16
- stub(builder).load_collation_elements_table { FRACTIONAL_UCA_SHORT_STUB }
17
- builder
18
- end
19
-
20
- it 'returns a trie' do
21
- trie_builder.is_a?(Trie)
22
- end
23
-
24
- it 'adds every collation element from the FractionalUCA_SHORT.txt file to the trie' do
25
- mock(Trie).new { TrieStub.new }
26
-
27
- trie_builder.build.storage.should == COLLATION_ELEMENTS_TABLE
28
- end
12
+ describe '#parse_trie' do
13
+ it 'returns a trie' do
14
+ TrieBuilder.parse_trie(fractional_uca_short_stub).should be_instance_of(Trie)
29
15
  end
30
- end
31
16
 
32
- end
17
+ it 'adds every collation element from the FCE table to the trie' do
18
+ trie = Object.new
19
+ mock(Trie).new { trie }
20
+ collation_elements_table.each { |code_points, collation_elements| mock(trie).set(code_points, collation_elements) }
33
21
 
34
- class TrieStub
35
- attr_accessor :storage
22
+ TrieBuilder.parse_trie(fractional_uca_short_stub).should == trie
23
+ end
36
24
 
37
- def initialize
38
- self.storage = []
39
- end
25
+ it 'populates the trie that is passed as an argument' do
26
+ trie = Object.new
27
+ collation_elements_table.each { |code_points, collation_elements| mock(trie).set(code_points, collation_elements) }
40
28
 
41
- def add(code_points, collation_element)
42
- storage << [code_points, collation_element]
43
- end
44
- end
29
+ TrieBuilder.parse_trie(fractional_uca_short_stub, trie).should == trie
30
+ end
45
31
 
46
- FRACTIONAL_UCA_SHORT_STUB = <<END
32
+ let(:fractional_uca_short_stub) do
33
+ <<END
47
34
  # Fractional UCA Table, generated from standard UCA
48
35
  # 2012-01-03, 21:52:55 GMT [MD]
49
36
  # VERSION: UCA=6.1.0, UCD=6.1.0
@@ -80,35 +67,128 @@ FFFF; [EF FE, 05, 05] # Special HIGHEST primary, for ranges
80
67
  [first tertiary in secondary non-ignorable [X, X, 05]] # U+0332 COMBINING LOW LINE
81
68
  [last tertiary in secondary non-ignorable [X, X, 3D]] # U+2A74 DOUBLE COLON EQUAL
82
69
  END
70
+ end
83
71
 
84
- COLLATION_ELEMENTS_TABLE = [
85
- # 0000; [,,]
86
- [[0], [[0, 0, 0]]],
72
+ let(:collation_elements_table) do
73
+ [
74
+ # 0000; [,,]
75
+ [[0], [[0, 0, 0]]],
87
76
 
88
- # 030C; [, 97, 05]
89
- [[780], [[0, 151, 5]]],
77
+ # 030C; [, 97, 05]
78
+ [[780], [[0, 151, 5]]],
90
79
 
91
- # 215E; [20, 05, 3B][0D 75 2C, 05, 3B][22, 05, 3D]
92
- [[8542], [[32, 5, 59], [881964, 5, 59], [34, 5, 61]]],
80
+ # 215E; [20, 05, 3B][0D 75 2C, 05, 3B][22, 05, 3D]
81
+ [[8542], [[32, 5, 59], [881964, 5, 59], [34, 5, 61]]],
93
82
 
94
- # FC63; [, D3 A9, 33][, D5 11, 33]
95
- [[64611], [[0, 54185, 51], [0, 54545, 51]]],
83
+ # FC63; [, D3 A9, 33][, D5 11, 33]
84
+ [[64611], [[0, 54185, 51], [0, 54545, 51]]],
96
85
 
97
- # 0E40 0E01; [72 0A, 05, 05][72 7E, 05, 3D]
98
- [[3648, 3585], [[29194, 5, 5], [29310, 5, 61]]],
86
+ # 0E40 0E01; [72 0A, 05, 05][72 7E, 05, 3D]
87
+ [[3648, 3585], [[29194, 5, 5], [29310, 5, 61]]],
99
88
 
100
- # 0E40 0E02; [72 0C, 05, 05][72 7E, 05, 3D]
101
- [[3648, 3586], [[29196, 5, 5], [29310, 5, 61]]],
89
+ # 0E40 0E02; [72 0C, 05, 05][72 7E, 05, 3D]
90
+ [[3648, 3586], [[29196, 5, 5], [29310, 5, 61]]],
102
91
 
103
- # FDD0 0063; [, 97, 3D]
104
- [[64976, 99], [[0, 151, 61]]],
92
+ # FDD0 0063; [, 97, 3D]
93
+ [[64976, 99], [[0, 151, 61]]],
105
94
 
106
- # FDD0 0064; [, A7, 09]
107
- [[64976, 100], [[0, 167, 9]]],
95
+ # FDD0 0064; [, A7, 09]
96
+ [[64976, 100], [[0, 167, 9]]],
97
+
98
+ # FFFE; [02, 02, 02]
99
+ [[65534], [[2, 2, 2]]],
100
+
101
+ # FFFF; [EF FE, 05, 05]
102
+ [[65535], [[61438, 5, 5]]]
103
+ ]
104
+ end
105
+ end
106
+
107
+ describe '#load_trie' do
108
+ it 'load FCE table from the resource into a trie' do
109
+ mock(TrieBuilder).parse_trie('fce-table') { 'trie' }
110
+ mock(TrieBuilder).load_resource('resource') { 'fce-table' }
111
+
112
+ TrieBuilder.load_trie('resource').should == 'trie'
113
+ end
114
+ end
115
+
116
+ describe '#load_tailored_trie' do
117
+ let(:locale) { :xxx }
118
+ let(:fallback) { TrieBuilder.parse_trie(fractional_uca_short_stub) }
119
+ let(:tailored_trie) { TrieBuilder.load_tailored_trie(locale, fallback) }
120
+
121
+ before(:each) { mock(TwitterCldr).get_resource(:collation, :tailoring, locale) { YAML.load(tailoring_resource_stub) } }
122
+
123
+ it 'returns a TrieWithFallback' do
124
+ tailored_trie.should be_instance_of(TrieWithFallback)
125
+ end
108
126
 
109
- # FFFE; [02, 02, 02]
110
- [[65534], [[2, 2, 2]]],
127
+ it 'tailors elements in the trie' do
128
+ fallback.get([0x0491]).should == [[0x5C1A, 5, 9], [0, 0xDBB9, 9]]
129
+ fallback.get([0x0490]).should == [[0x5C1A, 5, 0x93], [0, 0xDBB9, 9]]
130
+
131
+ tailored_trie.get([0x0491]).should == [[0x5C1B, 5, 5]]
132
+ tailored_trie.get([0x0490]).should == [[0x5C1B, 5, 0x86]]
133
+ end
134
+
135
+ it 'makes contractions available in the tailored trie' do
136
+ tailored_trie.get([0x491, 0x306]).should == [[0x5C, 0xDB, 9]]
137
+ tailored_trie.get([0x415, 0x306]).should == [[0x5C36, 5, 0x8F]]
138
+ end
139
+
140
+ it 'suppresses required contractions' do
141
+ fallback.find_prefix([0x41A, 0x301]).first(2).should == [[[0x5CCC, 5, 0x8F]], 2]
142
+ fallback.find_prefix([0x413, 0x301]).first(2).should == [[[0x5C30, 5, 0x8F]], 2]
143
+
144
+ tailored_trie.find_prefix([0x41A, 0x301]).first(2).should == [[[0x5C6C, 5, 0x8F]], 1]
145
+ tailored_trie.find_prefix([0x413, 0x301]).first(2).should == [[[0x5C1A, 5, 0x8F]], 1]
146
+ end
147
+
148
+ it 'do not copy other collation elements from the fallback' do
149
+ %w[0301 0306 041A 0413 0415].each do |code_point|
150
+ code_points = [code_point.to_i(16)]
151
+
152
+ tailored_trie.get(code_points).should_not be_nil
153
+ tailored_trie.get(code_points).object_id.should == fallback.get(code_points).object_id
154
+ end
155
+ end
156
+
157
+ let(:fractional_uca_short_stub) do
158
+ <<END
159
+ # collation elements from default FCE table
160
+ 0301; [, 8D, 05]
161
+ 0306; [, 91, 05]
162
+ 041A; [5C 6C, 05, 8F] # К
163
+ 0413; [5C 1A, 05, 8F] # Г
164
+ 0415; [5C 34, 05, 8F] # Е
165
+
166
+ # tailored (in UK locale) with "Г < ґ <<< Ґ"
167
+ 0491; [5C 1A, 05, 09][, DB B9, 09] # ґ
168
+ 0490; [5C 1A, 05, 93][, DB B9, 09] # Ґ
169
+
170
+ # contraction for a tailored collation element
171
+ 0491 0306; [5C, DB, 09] # ґ̆
172
+
173
+ # contractions suppressed in tailoring (for RU locale)
174
+ 041A 0301; [5C CC, 05, 8F] # Ќ
175
+ 0413 0301; [5C 30, 05, 8F] # Ѓ
176
+
177
+ # contractions non-suppressed in tailoring
178
+ 0415 0306; [5C 36, 05, 8F] # Ӗ
179
+ END
180
+ end
181
+
182
+ let(:tailoring_resource_stub) do
183
+ <<END
184
+ ---
185
+ :tailored_table: ! '0491; [5C1B, 5, 5]
186
+
187
+ 0490; [5C1B, 5, 86]'
188
+ :suppressed_contractions: ГК
189
+ ...
190
+ END
191
+ end
192
+ end
111
193
 
112
- # FFFF; [EF FE, 05, 05]
113
- [[65535], [[61438, 5, 5]]]
114
- ]
194
+ end
@@ -15,16 +15,67 @@ describe Trie do
15
15
  [
16
16
  [[1], '1' ],
17
17
  [[1, 4], '14' ],
18
- [[1, 5], '15' ],
19
18
  [[1, 4, 8], '148'],
19
+ [[1, 5], '15' ],
20
20
  [[2], '2' ],
21
21
  [[2, 7, 5], '275'],
22
- [[3, 9], '39' ]
22
+ [[3, 9, 2], '392'],
23
+ [[4], '4' ]
23
24
  ]
24
25
  end
25
26
 
26
- before(:each) do
27
- values.each { |key, value| trie.add(key, value) }
27
+ before(:each) { values.each { |key, value| trie.add(key, value) } }
28
+
29
+ describe '#initialize' do
30
+ it 'initializes an empty trie by default' do
31
+ Trie.new.should be_empty
32
+ end
33
+
34
+ it 'initializes with a root node' do
35
+ trie = Trie.new(Trie::Node.new(nil, 1 => Trie::Node.new(nil, { 2 => Trie::Node.new('12')}), 2 => Trie::Node.new('2')))
36
+
37
+ trie.to_hash.should == {
38
+ 1 => [nil, { 2 => ['12', {}] }],
39
+ 2 => ['2', {}]
40
+ }
41
+ end
42
+ end
43
+
44
+ describe '#lock and #locked?' do
45
+ it 'trie is unlocked by default' do
46
+ trie.should_not be_locked
47
+ end
48
+
49
+ it '#lock locks the trie' do
50
+ trie.lock
51
+ trie.should be_locked
52
+ end
53
+
54
+ it '#lock returns the trie' do
55
+ trie.lock.should == trie
56
+ end
57
+ end
58
+
59
+ describe '#starters' do
60
+ it 'returns all unique first elements of the keys in the trie' do
61
+ trie.starters.should =~ [1, 2, 3, 4]
62
+ end
63
+ end
64
+
65
+ describe '#each_starting_with' do
66
+ it 'iterates over all key-value pairs for which key starts with a given value' do
67
+ res = {}
68
+ trie.each_starting_with(1) { |k, v| res[k] = v }
69
+
70
+ res.should == { [1] => '1', [1, 4] => '14', [1, 5] => '15', [1, 4, 8] => '148' }
71
+ end
72
+
73
+ it 'works when argument is not a starter' do
74
+ res = {}
75
+ trie.each_starting_with(42) { |k, v| res[k] = v }
76
+
77
+ res.should == {}
78
+ end
28
79
  end
29
80
 
30
81
  describe '#get' do
@@ -32,29 +83,69 @@ describe Trie do
32
83
  [[6], [3], [1, 4, 3], [2, 7, 5, 6, 9]].each { |key| trie.get(key).should be_nil }
33
84
  end
34
85
 
35
- it 'returns value and key size for each existing key' do
86
+ it 'returns value for each existing key' do
36
87
  values.each { |key, value| trie.get(key).should == value }
37
88
  end
38
89
  end
39
90
 
40
91
  describe '#add' do
41
- it 'overrides values' do
92
+ it 'does not override values' do
42
93
  trie.get([1, 4]).should == '14'
43
94
 
44
95
  trie.add([1, 4], '14-new')
96
+ trie.get([1, 4]).should == '14'
97
+ end
98
+
99
+ it 'adds new values' do
100
+ trie.get([1, 9]).should be_nil
101
+
102
+ trie.add([1, 9], '19')
103
+ trie.get([1, 9]).should == '19'
104
+ end
105
+
106
+ it 'raises RuntimeError if called on a locked trie' do
107
+ lambda { trie.lock.add([1, 3], 'value') }.should raise_error(RuntimeError)
108
+ end
109
+ end
110
+
111
+ describe '#set' do
112
+ it 'overrides values' do
113
+ trie.get([1, 4]).should == '14'
114
+
115
+ trie.set([1, 4], '14-new')
45
116
  trie.get([1, 4]).should == '14-new'
46
117
  end
118
+
119
+ it 'adds new values' do
120
+ trie.get([1, 9]).should be_nil
121
+
122
+ trie.set([1, 9], '19')
123
+ trie.get([1, 9]).should == '19'
124
+ end
125
+
126
+ it 'raises RuntimeError if called on a locked trie' do
127
+ lambda { trie.lock.set([1, 3], 'value') }.should raise_error(RuntimeError)
128
+ end
47
129
  end
48
130
 
49
131
  describe '#find_prefix' do
50
- describe 'first (value) and third (prefix size) elements of the returned array' do
51
- it 'value is 0 nil and prefix size is 0 if the prefix was not found' do
52
- test_find_prefix(trie, [4], nil, 0)
132
+ let(:root_subtrie) {
133
+ {
134
+ 1 => ['1', { 4 => ['14', { 8 => ['148', {}] }], 5 => ['15', {}] }],
135
+ 2 => ['2', { 7 => [nil, { 5 => ['275', {}] }] }],
136
+ 3 => [nil, { 9 => [nil, { 2 => ['392', {}] }] }],
137
+ 4 => ['4', {}]
138
+ }
139
+ }
140
+
141
+ describe 'first two elements of the returned array (value and prefix size)' do
142
+ it 'are nil and 0 if the prefix was not found' do
143
+ trie.find_prefix([42]).first(2).should == [nil, 0]
53
144
  end
54
145
 
55
- it 'stored value and key size as a prefix size if the whole key was found' do
146
+ it 'are the stored value and the key size if the whole key was found' do
56
147
  values.each do |key, value|
57
- test_find_prefix(trie, key, value)
148
+ trie.find_prefix(key).first(2).should == [value, key.size]
58
149
  end
59
150
  end
60
151
 
@@ -66,32 +157,216 @@ describe Trie do
66
157
  [2, 7, 5, 5] => ['275', 3]
67
158
  }
68
159
 
69
- tests.each { |key, result| test_find_prefix(trie, key, *result) }
160
+ tests.each do |key, result|
161
+ trie.find_prefix(key).first(2).should == result
162
+ end
70
163
  end
71
164
 
72
165
  def test_find_prefix(trie, key, value, size = key.size)
73
- result = trie.find_prefix(key)
74
-
75
- result[0].should == value
76
- result[2].should == size
166
+ trie.find_prefix(key).first(2).should == [value, size]
77
167
  end
78
168
  end
79
169
 
80
- describe 'second (subtrie) element of the returned array' do
81
- it 'is a hash of possible suffixes for the prefix that was found' do
82
- trie.find_prefix([1, 4, 8])[1].should == {}
83
- trie.find_prefix([2, 7])[1].should == { 5 => ["275", { }] }
170
+ describe 'last element of the returned array (suffixes subtrie)' do
171
+ let(:non_existing_key) { [5, 2, 7] }
172
+ let(:key_with_suffixes) { [2] }
173
+ let(:key_without_suffixes) { [1, 4, 8] }
174
+
175
+ it 'is always a locked trie' do
176
+ [trie, trie.lock].each do |some_trie|
177
+ [non_existing_key, key_with_suffixes, key_without_suffixes].each do |key|
178
+ some_trie.find_prefix(key).last.should be_locked
179
+ end
180
+ end
181
+ end
182
+
183
+ it 'is a locked empty subtrie if the prefix that was found does not have any suffixes' do
184
+ trie.find_prefix(key_without_suffixes).last.to_hash.should be_empty
185
+ end
186
+
187
+ it 'is a subtrie of possible suffixes for the prefix that was found' do
188
+ trie.find_prefix(key_with_suffixes).last.to_hash.should == { 7 => [nil, { 5 => ["275", {}] }] }
84
189
  end
85
190
 
86
191
  it 'is a hash representing the whole trie if the prefix was not found' do
87
- trie.find_prefix([404])[1].should == {
88
- 1 => ['1', { 4 => ['14', { 8 => ['148', {}] }], 5 => ['15', {}] }],
89
- 2 => ['2', { 7 => [nil, { 5 => ['275', {}] }] }],
90
- 3 => [nil, { 9 => ['39', {}] }]
91
- }
192
+ trie.get(non_existing_key).should be_nil
193
+
194
+ trie.find_prefix(non_existing_key).last.to_hash.should == root_subtrie
195
+ end
196
+
197
+ end
198
+
199
+ context 'argument does not match any value, but is a prefix of a longer key' do
200
+ context 'argument has a shorter key as a prefix' do
201
+ it 'returns value for the key, its size and suffixes subtrie' do
202
+ trie.get([2]).should_not be_nil
203
+ trie.get([2, 7, 5]).should_not be_nil
204
+
205
+ result = trie.find_prefix([2, 7])
206
+
207
+ result.first(2).should == ['2', 1]
208
+ result.last.to_hash.should == { 7 => [nil, { 5 => ["275", {}] }] }
209
+ end
210
+ end
211
+
212
+ context 'argument does not have a shorter key as a prefix' do
213
+ it 'returns nil, 0 and suffixes subtrie for the root node' do
214
+ trie.get([3]).should be_nil
215
+ trie.get([3, 9]).should be_nil
216
+ trie.get([3, 9, 2]).should_not be_nil
217
+
218
+ result = trie.find_prefix([3, 9])
219
+
220
+ result.first(2).should == [nil, 0]
221
+ result.last.to_hash.should == root_subtrie
222
+ end
223
+ end
224
+ end
225
+
226
+ end
227
+
228
+ describe Trie::Node do
229
+
230
+ let(:node) { Trie::Node.new }
231
+ let(:child) { Trie::Node.new('child') }
232
+ let(:another_child) { Trie::Node.new('another-child') }
233
+
234
+ let(:root_node) do
235
+ Trie::Node.new(
236
+ 'node-0',
237
+ 1 => Trie::Node.new(
238
+ 'node-1',
239
+ 1 => Trie::Node.new('node-11'),
240
+ 2 => Trie::Node.new('node-12')
241
+ ),
242
+ 2 => Trie::Node.new(
243
+ 'node-2',
244
+ 1 => Trie::Node.new(
245
+ 'node-21',
246
+ 1 => Trie::Node.new('node-211')
247
+ )
248
+ )
249
+ )
250
+ end
251
+
252
+ let(:subtrie_hash) do
253
+ {
254
+ 1 => [
255
+ 'node-1',
256
+ {
257
+ 1 => ['node-11', {}],
258
+ 2 => ['node-12', {}]
259
+ }
260
+ ],
261
+ 2 => [
262
+ 'node-2',
263
+ {
264
+ 1 => [
265
+ 'node-21',
266
+ {
267
+ 1 => ['node-211', {}]
268
+ }
269
+ ]
270
+ }
271
+ ]
272
+ }
273
+ end
274
+
275
+ describe '#initialize' do
276
+ it 'initializes node with nil value and empty children hash by default' do
277
+ node.value.should be_nil
278
+ node.should_not have_children
279
+ end
280
+
281
+ it 'initializes node with provided value and children hash' do
282
+ root_node.value.should == 'node-0'
283
+ root_node.should have_children
284
+ end
285
+ end
286
+
287
+ describe '#child and #set_child' do
288
+ it '#child returns nil if a child with a given key does not exist' do
289
+ node.child(42).should be_nil
290
+ end
291
+
292
+ it '#set_child saves a child by key and #child returns the child by key' do
293
+ node.set_child(42, child)
294
+ node.child(42).should == child
295
+ end
296
+
297
+ it '#set_child overrides a child by key' do
298
+ node.set_child(42, child)
299
+ node.set_child(42, another_child)
300
+
301
+ node.child(42).should_not == child
302
+ node.child(42).should == another_child
303
+ end
304
+
305
+ it '#set_child returns the child that was saved' do
306
+ node.set_child(42, child).should == child
307
+ end
308
+ end
309
+
310
+ describe '#each_key_and_child' do
311
+ it 'iterates over all (key, child) pairs' do
312
+ node.set_child(42, child)
313
+ node.set_child(13, another_child)
314
+ res = {}
315
+ node.each_key_and_child { |key, child| res[key] = child }
316
+
317
+ res.should == { 42 => child, 13 => another_child }
318
+ end
319
+ end
320
+
321
+ describe '#keys' do
322
+ it 'returns all children keys' do
323
+ node.set_child(42, child)
324
+ node.set_child(13, another_child)
325
+
326
+ node.keys.should =~ [13, 42]
327
+ end
328
+ end
329
+
330
+ describe '#has_children?' do
331
+ it 'returns false if the node has no children' do
332
+ node.should_not have_children
333
+ end
334
+
335
+ it 'returns true if the node has children' do
336
+ node.set_child(42, child)
337
+ node.should have_children
338
+ end
339
+ end
340
+
341
+ describe '#to_trie' do
342
+ it 'returns a trie' do
343
+ node.to_trie.should be_instance_of(Trie)
344
+ end
345
+
346
+ it 'returns a locked trie' do
347
+ node.to_trie.should be_locked
348
+ end
349
+
350
+ it 'current node is a root of a new trie' do
351
+ root_node.to_trie.to_hash.should == subtrie_hash
352
+ end
353
+
354
+ it 'sets new trie root value to nil' do
355
+ root_node.value.should_not be_nil
356
+ root_node.to_trie.instance_variable_get(:@root).value.should be_nil
357
+ end
358
+ end
359
+
360
+ describe '#subtrie_hash' do
361
+ it 'returns an empty hash if the node has no children' do
362
+ node.subtrie_hash.should == {}
363
+ end
364
+
365
+ it 'returns a nested hash of children values' do
366
+ root_node.subtrie_hash.should == subtrie_hash
92
367
  end
93
368
  end
94
369
 
95
370
  end
96
371
 
97
- end
372
+ end