tr8n_core 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +69 -0
  4. data/Rakefile +9 -0
  5. data/config/config.yml +34 -0
  6. data/config/tokens/data.yml +45 -0
  7. data/config/tokens/decorations.yml +37 -0
  8. data/lib/tr8n/application.rb +320 -0
  9. data/lib/tr8n/base.rb +123 -0
  10. data/lib/tr8n/cache.rb +144 -0
  11. data/lib/tr8n/cache_adapters/cdb.rb +74 -0
  12. data/lib/tr8n/cache_adapters/file.rb +70 -0
  13. data/lib/tr8n/cache_adapters/memcache.rb +91 -0
  14. data/lib/tr8n/cache_adapters/redis.rb +94 -0
  15. data/lib/tr8n/component.rb +68 -0
  16. data/lib/tr8n/config.rb +291 -0
  17. data/lib/tr8n/decorators/base.rb +35 -0
  18. data/lib/tr8n/decorators/default.rb +30 -0
  19. data/lib/tr8n/decorators/html.rb +63 -0
  20. data/lib/tr8n/exception.rb +26 -0
  21. data/lib/tr8n/language.rb +250 -0
  22. data/lib/tr8n/language_case.rb +116 -0
  23. data/lib/tr8n/language_case_rule.rb +85 -0
  24. data/lib/tr8n/language_context.rb +115 -0
  25. data/lib/tr8n/language_context_rule.rb +62 -0
  26. data/lib/tr8n/logger.rb +83 -0
  27. data/lib/tr8n/rules_engine/evaluator.rb +156 -0
  28. data/lib/tr8n/rules_engine/parser.rb +83 -0
  29. data/lib/tr8n/source.rb +95 -0
  30. data/lib/tr8n/tokens/data.rb +410 -0
  31. data/lib/tr8n/tokens/data_tokenizer.rb +82 -0
  32. data/lib/tr8n/tokens/decoration_tokenizer.rb +200 -0
  33. data/lib/tr8n/tokens/hidden.rb +48 -0
  34. data/lib/tr8n/tokens/method.rb +52 -0
  35. data/lib/tr8n/tokens/transform.rb +191 -0
  36. data/lib/tr8n/translation.rb +104 -0
  37. data/lib/tr8n/translation_key.rb +205 -0
  38. data/lib/tr8n/translator.rb +62 -0
  39. data/lib/tr8n/utils.rb +124 -0
  40. data/lib/tr8n_core/ext/array.rb +74 -0
  41. data/lib/tr8n_core/ext/date.rb +63 -0
  42. data/lib/tr8n_core/ext/fixnum.rb +39 -0
  43. data/lib/tr8n_core/ext/hash.rb +126 -0
  44. data/lib/tr8n_core/ext/string.rb +44 -0
  45. data/lib/tr8n_core/ext/time.rb +71 -0
  46. data/lib/tr8n_core/generators/cache/base.rb +85 -0
  47. data/lib/tr8n_core/generators/cache/cdb.rb +27 -0
  48. data/lib/tr8n_core/generators/cache/file.rb +69 -0
  49. data/lib/tr8n_core/modules/logger.rb +43 -0
  50. data/lib/tr8n_core/version.rb +27 -0
  51. data/lib/tr8n_core.rb +68 -0
  52. data/spec/application_spec.rb +228 -0
  53. data/spec/base_spec.rb +19 -0
  54. data/spec/config_spec.rb +16 -0
  55. data/spec/decorator_spec.rb +10 -0
  56. data/spec/decorators/base_spec.rb +14 -0
  57. data/spec/decorators/default_spec.rb +12 -0
  58. data/spec/decorators/html_spec.rb +50 -0
  59. data/spec/fixtures/application.json +112 -0
  60. data/spec/fixtures/languages/en-US.json +1424 -0
  61. data/spec/fixtures/languages/es.json +291 -0
  62. data/spec/fixtures/languages/ru.json +550 -0
  63. data/spec/fixtures/translations/ru/basic.json +56 -0
  64. data/spec/fixtures/translations/ru/counters.json +43 -0
  65. data/spec/fixtures/translations/ru/genders.json +171 -0
  66. data/spec/fixtures/translations/ru/last_names.txt +200 -0
  67. data/spec/fixtures/translations/ru/names.json +1 -0
  68. data/spec/fixtures/translations/ru/names.txt +458 -0
  69. data/spec/helper.rb +84 -0
  70. data/spec/language_case_rule_spec.rb +57 -0
  71. data/spec/language_case_spec.rb +58 -0
  72. data/spec/language_context_rule_spec.rb +73 -0
  73. data/spec/language_context_spec.rb +331 -0
  74. data/spec/language_spec.rb +16 -0
  75. data/spec/rules_engine/evaluator_spec.rb +148 -0
  76. data/spec/rules_engine/parser_spec.rb +29 -0
  77. data/spec/tokens/data_spec.rb +160 -0
  78. data/spec/tokens/data_tokenizer_spec.rb +29 -0
  79. data/spec/tokens/decoration_tokenizer_spec.rb +81 -0
  80. data/spec/tokens/hidden_spec.rb +24 -0
  81. data/spec/tokens/method_spec.rb +84 -0
  82. data/spec/tokens/transform_spec.rb +50 -0
  83. data/spec/translation_key_spec.rb +96 -0
  84. data/spec/translation_spec.rb +24 -0
  85. data/spec/utils_spec.rb +64 -0
  86. metadata +176 -0
@@ -0,0 +1,458 @@
1
+ Авдотья
2
+
3
+ Аким
4
+
5
+ Аксинья
6
+
7
+ Алевтина
8
+
9
+ Александр
10
+
11
+ Александра
12
+
13
+ Алексей
14
+
15
+ Алена
16
+
17
+ Алина
18
+
19
+ Алла
20
+
21
+ Анастасия
22
+
23
+ Анатолий
24
+
25
+ Ангелина
26
+
27
+ Андрей
28
+
29
+ Анисья
30
+
31
+ Анна
32
+
33
+ Антон
34
+
35
+ Антонина
36
+
37
+ Анфим
38
+
39
+ Анфиса
40
+
41
+ Аполлинария
42
+
43
+ Арина
44
+
45
+ Аркадий
46
+
47
+ Арсений
48
+
49
+ Артем
50
+
51
+ Артемий
52
+
53
+ Богдан
54
+
55
+ Богдана
56
+
57
+ Борис
58
+
59
+ Борислав
60
+
61
+ Вадим
62
+
63
+ Валентин
64
+
65
+ Валентина
66
+
67
+ Валерий
68
+
69
+ Валерия
70
+
71
+ Варвара
72
+
73
+ Василий
74
+
75
+ Василиса
76
+
77
+ Венера
78
+
79
+ Вера
80
+
81
+ Вета
82
+
83
+ Виктор
84
+
85
+ Викторина
86
+
87
+ Виктория
88
+
89
+ Вилена
90
+
91
+ Виолетта
92
+
93
+ Виталий
94
+
95
+ Виталина
96
+ Виталия
97
+
98
+ Влада
99
+
100
+ Владана
101
+
102
+ Владимир
103
+
104
+ Владислав
105
+
106
+ Владислава
107
+
108
+ Владлен
109
+
110
+ Всеволод
111
+
112
+ Вячеслав
113
+
114
+ Галина
115
+
116
+ Геннадий
117
+
118
+ Георгий
119
+
120
+ Герасим
121
+
122
+ Глеб
123
+
124
+ Гордей
125
+
126
+ Григорий
127
+
128
+ Дамир
129
+
130
+ Даниил
131
+
132
+ Данислав
133
+
134
+ Дарья
135
+
136
+ Денис
137
+
138
+ Джереми (Иеремия)
139
+
140
+ Дина
141
+
142
+ Дмитрий
143
+
144
+ Евгений
145
+
146
+ Евгения
147
+
148
+ Евдоким
149
+
150
+ Евдокия
151
+
152
+ Евстахий
153
+
154
+ Егор
155
+
156
+ Екатерина
157
+
158
+ Елена
159
+
160
+ Елизавета
161
+
162
+ Елисей
163
+
164
+ Емельян
165
+
166
+ Еремей
167
+
168
+ Есения
169
+
170
+ Ефим
171
+
172
+ Захар
173
+
174
+ Зинаида
175
+
176
+ Зиновий
177
+
178
+ Злата
179
+
180
+ Зоя
181
+
182
+ Иван
183
+
184
+ Игнат
185
+
186
+ Игнатий
187
+
188
+ Игорь
189
+
190
+ Иероним (Джером)
191
+
192
+ Изабелла
193
+
194
+ Иллирика
195
+
196
+ Илья
197
+
198
+ Инесса
199
+
200
+ Инна
201
+
202
+ Иннокентий
203
+
204
+ Иоанна
205
+
206
+ Ира
207
+
208
+ Ираида
209
+
210
+ Ирина
211
+
212
+ Искра
213
+
214
+ Ия
215
+
216
+ Карина
217
+
218
+ Кира
219
+
220
+ Кирилл
221
+
222
+ Клим
223
+
224
+ Константин
225
+
226
+ Кристина
227
+
228
+ Ксения
229
+
230
+ Кузьма
231
+
232
+ Лада
233
+
234
+ Лара
235
+
236
+ Лариса
237
+
238
+ Лев
239
+
240
+ Леонид
241
+
242
+ Лера
243
+
244
+ Лидия
245
+
246
+ Лика
247
+
248
+ Любовь
249
+
250
+ Людмила
251
+
252
+ Ляля
253
+
254
+ Магдалeна
255
+
256
+ Майя
257
+
258
+ Макар
259
+
260
+ Макарий
261
+
262
+ Макария
263
+
264
+ Максим
265
+
266
+ Маргарита
267
+
268
+ Марина
269
+
270
+ Мария
271
+
272
+ Марк
273
+
274
+ Мартин (Мартын)
275
+
276
+ Марфа
277
+
278
+ Матвей
279
+
280
+ Мила
281
+
282
+ Милада
283
+
284
+ Милана
285
+
286
+ Милена
287
+
288
+ Милица
289
+
290
+ Мира
291
+
292
+ Мирослав
293
+
294
+ Мирослава
295
+
296
+ Мирра
297
+
298
+ Михаил
299
+
300
+ Надежда
301
+
302
+ Наталья
303
+
304
+ Нелли
305
+
306
+ Ника
307
+
308
+ Никита
309
+
310
+ Никодим
311
+
312
+ Никола
313
+
314
+ Николай
315
+
316
+ Нина
317
+
318
+ Нинель
319
+
320
+ Оксана
321
+
322
+ Олег
323
+
324
+ Олеся
325
+
326
+ Ольга
327
+
328
+ Осип (Иосиф)
329
+
330
+ Остап
331
+
332
+ Павел
333
+
334
+ Павлина
335
+
336
+ Пелагея
337
+
338
+ Петр
339
+
340
+ Платон
341
+
342
+ Платонида
343
+
344
+ Полина
345
+
346
+ Прасковья
347
+
348
+ Прохор
349
+
350
+ Рада
351
+
352
+ Радий
353
+
354
+ Радик
355
+
356
+ Радомир
357
+
358
+ Радослав
359
+
360
+ Раиса
361
+
362
+ Рената
363
+
364
+ Римма
365
+
366
+ Ренат
367
+
368
+ Родион
369
+
370
+ Роман
371
+
372
+ Ростислав
373
+
374
+ Русалина
375
+
376
+ Руслан
377
+
378
+ Сабина
379
+
380
+ Савва
381
+
382
+ Савелий
383
+
384
+ Светлана
385
+
386
+ Святослав
387
+
388
+ Севастьян
389
+
390
+ Семен
391
+
392
+ Серафима
393
+
394
+ Сергей
395
+
396
+ Сидор
397
+
398
+ Соня
399
+
400
+ Софья
401
+
402
+ Спартак
403
+
404
+ Станислав
405
+
406
+ Стелла
407
+
408
+ Степан
409
+
410
+ Таисия
411
+
412
+ Тарас
413
+
414
+ Татьяна
415
+
416
+ Тимофей
417
+
418
+ Тихон
419
+
420
+ Трофим
421
+
422
+ Ульяна
423
+
424
+ Фаина
425
+
426
+ Федор
427
+
428
+ Федот
429
+
430
+ Филипп
431
+
432
+ Флор
433
+
434
+ Фома
435
+
436
+ Харитон
437
+
438
+ Цветана
439
+
440
+ Юлиан
441
+
442
+ Юлия
443
+
444
+ Юния
445
+
446
+ Юрий
447
+
448
+ Яков
449
+
450
+ Яна
451
+
452
+ Янина
453
+
454
+ Ярина
455
+
456
+ Ярослав
457
+
458
+ Ярослава
data/spec/helper.rb ADDED
@@ -0,0 +1,84 @@
1
+ require 'rspec'
2
+ require 'json'
3
+
4
+ require 'simplecov'
5
+ require 'coveralls'
6
+
7
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
8
+ SimpleCov::Formatter::HTMLFormatter,
9
+ Coveralls::SimpleCov::Formatter
10
+ ]
11
+ SimpleCov.start
12
+
13
+ require 'tr8n_core'
14
+
15
+ def fixtures_root
16
+ File.join(File.dirname(__FILE__), 'fixtures')
17
+ end
18
+
19
+ def load_json(file_path)
20
+ JSON.parse(File.read("#{fixtures_root}/#{file_path}"))
21
+ end
22
+
23
+ def load_translation_keys_from_file(app, path)
24
+ load_json(path).each do |jkey|
25
+ load_translation_key_from_hash(app, jkey)
26
+ end
27
+ end
28
+
29
+ def load_translation_key_from_hash(app, hash)
30
+ app.cache_translation_key(Tr8n::TranslationKey.new(hash.merge(:application => app)))
31
+ end
32
+
33
+ def stub_object(attrs)
34
+ user = double()
35
+ attrs.each do |key, value|
36
+ user.stub(key) { value }
37
+ end
38
+ user
39
+ end
40
+
41
+ def init_application(locales = [], path = 'application.json')
42
+ locales = ['en-US', 'ru', 'es'] if locales.size == 0
43
+ app = Tr8n::Application.new(load_json(path))
44
+ locales.each do |locale|
45
+ app.add_language(Tr8n::Language.new(load_json("languages/#{locale}.json")))
46
+ end
47
+ Tr8n.config.application = app
48
+ Tr8n.config.current_language = app.language('en-US')
49
+ app
50
+ end
51
+
52
+ RSpec.configure do |config|
53
+ config.before do
54
+ ARGV.replace []
55
+ end
56
+
57
+ config.expect_with :rspec do |c|
58
+ c.syntax = :expect
59
+ end
60
+
61
+ def capture(stream)
62
+ begin
63
+ stream = stream.to_s
64
+ eval "$#{stream} = StringIO.new"
65
+ yield
66
+ result = eval("$#{stream}").string
67
+ ensure
68
+ eval("$#{stream} = #{stream.upcase}")
69
+ end
70
+
71
+ result
72
+ end
73
+
74
+ def source_root
75
+ fixtures_root
76
+ end
77
+
78
+ def destination_root
79
+ File.join(File.dirname(__FILE__), 'sandbox')
80
+ end
81
+
82
+ alias :silence :capture
83
+ end
84
+
@@ -0,0 +1,57 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'helper'
4
+
5
+ describe Tr8n::LanguageCaseRule do
6
+ describe "initialize" do
7
+ it "sets attributes" do
8
+ expect(Tr8n::LanguageCaseRule.attributes).to eq([:language_case, :id, :description, :examples, :conditions, :conditions_expression, :operations, :operations_expression])
9
+ end
10
+ end
11
+
12
+ describe "evaluating simple rules without genders" do
13
+ it "should result in correct substitution" do
14
+ @rule = Tr8n::LanguageCaseRule.new(
15
+ :conditions => "(match '/s$/' @value)",
16
+ :operations => "(append \"'\" @value)"
17
+ )
18
+
19
+ expect(@rule.evaluate("Michael")).to be_false
20
+ expect(@rule.evaluate("Anna")).to be_false
21
+
22
+ expect(@rule.evaluate("friends")).to be_true
23
+ expect(@rule.apply("friends")).to eq("friends'")
24
+
25
+ @rule = Tr8n::LanguageCaseRule.new(
26
+ :conditions => "(not (match '/s$/' @value))",
27
+ :operations => "(append \"'s\" @value)"
28
+ )
29
+
30
+ expect(@rule.evaluate("Michael")).to be_true
31
+ expect(@rule.apply("Michael")).to eq("Michael's")
32
+
33
+ expect(@rule.evaluate("Anna")).to be_true
34
+ expect(@rule.apply("Anna")).to eq("Anna's")
35
+
36
+ expect(@rule.evaluate("friends")).to be_false
37
+
38
+ @rule = Tr8n::LanguageCaseRule.new(
39
+ :conditions => "(= '1' @value))",
40
+ :operations => "(quote 'first')"
41
+ )
42
+
43
+ expect(@rule.evaluate('2')).to be_false
44
+ expect(@rule.evaluate('1')).to be_true
45
+ expect(@rule.apply('1')).to eq("first")
46
+
47
+ @rule = Tr8n::LanguageCaseRule.new(
48
+ :conditions => "(match '/(0|4|5|6|7|8|9|11|12|13)$/' @value))",
49
+ :operations => "(append 'th' @value)"
50
+ )
51
+
52
+ expect(@rule.apply('4')).to eq("4th")
53
+ expect(@rule.apply('15')).to eq("15th")
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'helper'
4
+
5
+ describe Tr8n::LanguageCase do
6
+ before do
7
+ @app = init_application
8
+ @english = @app.language('en-US')
9
+ @russian = @app.language('ru')
10
+ end
11
+
12
+ describe "initialize" do
13
+ it "sets attributes" do
14
+ expect(Tr8n::LanguageCase.attributes).to eq([:language, :id, :keyword, :latin_name, :native_name, :description, :application, :rules])
15
+ end
16
+ end
17
+
18
+ describe "apply case" do
19
+ it "should return correct data" do
20
+ lcase = Tr8n::LanguageCase.new(
21
+ :language => @english,
22
+ :keyword => "pos",
23
+ :latin_name => "Possessive",
24
+ :native_name => "Possessive",
25
+ :description => "Used to indicate possession (i.e., ownership). It is usually created by adding 's to the word",
26
+ :application => "phrase"
27
+ )
28
+
29
+ lcase.rules << Tr8n::LanguageCaseRule.new(:conditions => "(match '/s$/' @value)", :operations => "(append \"'\" @value)")
30
+ lcase.rules << Tr8n::LanguageCaseRule.new(:conditions => "(not (match '/s$/' @value))", :operations => "(append \"'s\" @value)")
31
+ expect(lcase.apply("Michael")).to eq("Michael's")
32
+ end
33
+
34
+ it "should correctly process default cases" do
35
+ possessive = @english.language_case_by_keyword('pos')
36
+ expect(possessive.apply("Michael")).to eq("Michael's")
37
+
38
+
39
+ plural = @english.language_case_by_keyword('plural')
40
+
41
+ expect(plural.apply("fish")).to eq("fish")
42
+ expect(plural.apply("money")).to eq("money")
43
+
44
+ # irregular
45
+ expect(plural.apply("move")).to eq("moves")
46
+
47
+ # plurals
48
+ expect(plural.apply("quiz")).to eq("quizzes")
49
+ expect(plural.apply("wife")).to eq("wives")
50
+
51
+ singular = @english.language_case_by_keyword('singular')
52
+ expect(singular.apply("quizzes")).to eq("quiz")
53
+ expect(singular.apply("cars")).to eq("car")
54
+ expect(singular.apply("wives")).to eq("wife")
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,73 @@
1
+ require 'helper'
2
+
3
+ describe Tr8n::LanguageContextRule do
4
+
5
+ describe "initialize" do
6
+ it "sets attributes" do
7
+ expect(Tr8n::LanguageContextRule.attributes).to eq([:language_context, :keyword, :description, :examples, :conditions, :conditions_expression])
8
+ end
9
+ end
10
+
11
+ describe "finding fallback rules" do
12
+ it "should return fallback rule" do
13
+ rule = Tr8n::LanguageContextRule.new(:keyword => "one", :conditions => "(= 1 @n)", :examples => "1")
14
+ expect(rule.fallback?).to be_false
15
+
16
+ rule = Tr8n::LanguageContextRule.new(:keyword => "other", :examples => "0, 2-999; 1.2, 2.07...")
17
+ expect(rule.fallback?).to be_true
18
+ end
19
+ end
20
+
21
+ describe "evaluating rules" do
22
+ it "should return correct results" do
23
+ rule = Tr8n::LanguageContextRule.new(:keyword => "one", :conditions => "(= 1 @n)", :examples => "1")
24
+ expect(rule.evaluate).to be_false
25
+ expect(rule.evaluate({"@n" => 1})).to be_true
26
+ expect(rule.evaluate({"@n" => 2})).to be_false
27
+ expect(rule.evaluate({"@n" => 0})).to be_false
28
+
29
+ one = Tr8n::LanguageContextRule.new(:keyword => "one", :conditions => "(&& (= 1 (mod @n 10)) (!= 11 (mod @n 100)))", :description => "{n} mod 10 is 1 and {n} mod 100 is not 11", :examples => "1, 21, 31, 41, 51, 61...")
30
+ few = Tr8n::LanguageContextRule.new(:keyword => "few", :conditions => "(&& (in '2..4' (mod @n 10)) (not (in '12..14' (mod @n 100))))", :description => "{n} mod 10 in 2..4 and {n} mod 100 not in 12..14", :examples => "2-4, 22-24, 32-34...")
31
+ many = Tr8n::LanguageContextRule.new(:keyword => "many", :conditions => "(|| (= 0 (mod @n 10)) (in '5..9' (mod @n 10)) (in '11..14' (mod @n 100)))", :description => "{n} mod 10 is 0 or {n} mod 10 in 5..9 or {n} mod 100 in 11..14", :examples => "0, 5-20, 25-30, 35-40...")
32
+
33
+ {
34
+ [1, 21, 31, 101, 121] => one,
35
+ [2, 3, 4, 22, 23, 24, 102, 103, 104] => few,
36
+ [0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 20, 25, 26, 28, 30, 35, 36, 38, 39, 40] => many
37
+ }.each do |vals, rule|
38
+ vals.each do |val|
39
+ vars = {"@n" => val}
40
+ expect(rule.evaluate(vars)).to be_true
41
+ end
42
+ end
43
+
44
+ {
45
+ [2, 3, 4, 9] => one,
46
+ [5, 6, 7, 8, 9] => few,
47
+ [1, 2, 3, 4] => many
48
+ }.each do |vals, rule|
49
+ vals.each do |val|
50
+ vars = {"@n" => val}
51
+ expect(rule.evaluate(vars)).to be_false
52
+ end
53
+ end
54
+
55
+ one_male = Tr8n::LanguageContextRule.new(:keyword => "one_male", :conditions => "(&& (= 1 (count @genders)) (all @genders 'male'))", :description => "List contains one male user")
56
+ one_female = Tr8n::LanguageContextRule.new(:keyword => "one_female", :conditions => "(&& (= 1 (count @genders)) (all @genders 'female'))", :description => "List contains one female user")
57
+ one_unknown = Tr8n::LanguageContextRule.new(:keyword => "one_unknown", :conditions => "(&& (= 1 (count @genders)) (all @genders 'unknown'))", :description => "List contains one user with unknown gender")
58
+ many = Tr8n::LanguageContextRule.new(:keyword => "many", :conditions => "(> (count @genders) 1)", :description => "List contains two or more users")
59
+
60
+ expect(one_male.evaluate({"@genders" => ["male"]})).to be_true
61
+ expect(one_male.evaluate({"@genders" => ["male", "male"]})).to be_false
62
+
63
+ expect(one_female.evaluate({"@genders" => ["female"]})).to be_true
64
+ expect(many.evaluate({"@genders" => ["female", "male"]})).to be_true
65
+
66
+ expect(one_unknown.evaluate({"@genders" => ["unknown"]})).to be_true
67
+
68
+ many_male = Tr8n::LanguageContextRule.new(:keyword => "one", :conditions => "(&& (> (count @genders) 1) (all @genders 'male'))", :description => "List contains at least two users, all male")
69
+ expect(many_male.evaluate({"@genders" => ["male", "male"]})).to be_true
70
+ end
71
+ end
72
+
73
+ end