text_alignment 0.6.3 → 0.7.3
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.
- checksums.yaml +4 -4
- data/bin/align_annotations +4 -19
- data/lib/text_alignment/constants.rb +1 -1
- data/lib/text_alignment/mappings.rb +1 -0
- data/lib/text_alignment/text_alignment.rb +198 -105
- data/lib/text_alignment/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0788bdb6d161499f5a5258757b9f61faee96b60246b422b57b17ba953b4a2c87'
|
4
|
+
data.tar.gz: 4564fd15e1e1d673932438206989dc706aa67f2467698f16946e2635c562ec90
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c93882cc28e8bfdbdeed325b282b63fd3f3644d1739ed979ef92a7d8a133e26f4a8ffd0bda22da0fc2e0a31c77c6d49ce89f8f0fae802c9ae2041a7db60a2a4e
|
7
|
+
data.tar.gz: 238ed44ac0c0a178a64743846550639c626d3a300a79f05fedb016e71a890f11025b52d260ce2f46f00d68b1c4b9c13c48a8be8712f737bb2b57b0274b174b8b
|
data/bin/align_annotations
CHANGED
@@ -105,9 +105,7 @@ lost_annotations = []
|
|
105
105
|
target_annotations = if source_annotations.class == Array
|
106
106
|
align_mdoc(source_annotations, {text: target_text})
|
107
107
|
else
|
108
|
-
alignment = TextAlignment::TextAlignment.new(source_annotations[:text], target_text)
|
109
|
-
|
110
|
-
# pp alignment
|
108
|
+
alignment = TextAlignment::TextAlignment.new(source_annotations[:text], target_text, source_annotations[:denotations])
|
111
109
|
|
112
110
|
# verification
|
113
111
|
# source_text = source_annotations[:text]
|
@@ -142,22 +140,7 @@ else
|
|
142
140
|
puts "====="
|
143
141
|
# exit
|
144
142
|
|
145
|
-
# verification of source denotations
|
146
|
-
puts "[Invalid source denotations]"
|
147
|
-
source_annotations[:denotations] do |d|
|
148
|
-
p d unless d[:span][:begin] && d[:span][:end] && d[:span][:begin] < d[:span][:end] && d[:span][:begin] >= 0 && d[:span][:end] < source_text.length
|
149
|
-
end
|
150
|
-
puts "====="
|
151
|
-
puts
|
152
|
-
|
153
143
|
denotations = alignment.transform_hdenotations(source_annotations[:denotations])
|
154
|
-
puts "[Invalid transformation]"
|
155
|
-
denotations.each do |d|
|
156
|
-
p d unless d[:span][:begin] && d[:span][:end] && d[:span][:begin] < d[:span][:end] && d[:span][:begin] >= 0 && d[:span][:end] < target_text.length
|
157
|
-
end
|
158
|
-
puts "====="
|
159
|
-
puts
|
160
|
-
|
161
144
|
lost_annotations += alignment.lost_annotations if alignment.lost_annotations
|
162
145
|
|
163
146
|
source_annotations.merge({text:target_text, denotations:denotations})
|
@@ -194,7 +177,9 @@ warn "denotations:\t#{target_annotations[:denotations].nil? ? 0 : target_annotat
|
|
194
177
|
|
195
178
|
if lost_annotations
|
196
179
|
warn "\n[lost annotations]"
|
197
|
-
|
180
|
+
lost_annotations.each do |a|
|
181
|
+
p a
|
182
|
+
end
|
198
183
|
end
|
199
184
|
|
200
185
|
#puts target_annotations.to_json
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module TextAlignment; end unless defined? TextAlignment
|
2
2
|
|
3
3
|
TextAlignment::SIZE_NGRAM = 8 unless defined? TextAlignment::SIZE_NGRAM
|
4
|
-
TextAlignment::SIZE_WINDOW =
|
4
|
+
TextAlignment::SIZE_WINDOW = 30 unless defined? TextAlignment::SIZE_WINDOW
|
5
5
|
TextAlignment::BUFFER_RATE = 0.1 unless defined? TextAlignment::BUFFER_RATE
|
6
6
|
TextAlignment::BUFFER_MIN = 20 unless defined? TextAlignment::BUFFER_MIN
|
7
7
|
TextAlignment::TEXT_SIMILARITY_THRESHOLD = 0.9 unless defined? TextAlignment::TEXT_SIMILARITY_THRESHOLD
|
@@ -63,6 +63,7 @@ TextAlignment::MAPPINGS = [
|
|
63
63
|
[" ", " "], #U+200A (hair space)
|
64
64
|
[" ", " "], #U+00A0 (no-break space)
|
65
65
|
[" ", " "], #U+3000 (ideographic space)
|
66
|
+
["‑", "-"], #U+2211 (Non-Breaking Hyphen)
|
66
67
|
["−", "-"], #U+2212 (minus sign)
|
67
68
|
["–", "-"], #U+2013 (en dash)
|
68
69
|
["′", "'"], #U+2032 (prime)
|
@@ -12,43 +12,46 @@ class TextAlignment::TextAlignment
|
|
12
12
|
attr_reader :similarity
|
13
13
|
attr_reader :lost_annotations
|
14
14
|
|
15
|
-
def initialize(str1, str2, _size_ngram = nil, _size_window = nil, _text_similiarity_threshold = nil)
|
15
|
+
def initialize(str1, str2, denotations = nil, _size_ngram = nil, _size_window = nil, _text_similiarity_threshold = nil)
|
16
16
|
raise ArgumentError, "nil string" if str1.nil? || str2.nil?
|
17
17
|
|
18
18
|
@block_alignment = {source_text:str1, target_text:str2}
|
19
|
+
@str1 = str1
|
20
|
+
@str2 = str2
|
19
21
|
|
20
|
-
|
22
|
+
## Block exact match
|
21
23
|
block_begin = str2.index(str1)
|
22
24
|
unless block_begin.nil?
|
23
25
|
@block_alignment[:blocks] = [{source:{begin:0, end:str1.length}, target:{begin:block_begin, end:block_begin + str1.length}, delta:block_begin, alignment: :block}]
|
24
|
-
return
|
26
|
+
return
|
25
27
|
end
|
26
28
|
|
27
|
-
# try exact match
|
28
29
|
block_begin = str2.downcase.index(str1.downcase)
|
29
30
|
unless block_begin.nil?
|
30
31
|
@block_alignment[:blocks] = [{source:{begin:0, end:str1.length}, target:{begin:block_begin, end:block_begin + str1.length}, delta:block_begin, alignment: :block}]
|
31
|
-
return
|
32
|
+
return
|
32
33
|
end
|
33
34
|
|
35
|
+
|
36
|
+
## to find block alignments
|
34
37
|
anchor_finder = TextAlignment::AnchorFinder.new(str1, str2, _size_ngram, _size_window, _text_similiarity_threshold)
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
last
|
40
|
-
|
41
|
-
last[:
|
42
|
-
last[:target][:end] = anchor[:target][:end]
|
39
|
+
blocks = []
|
40
|
+
while block = anchor_finder.get_next_anchor
|
41
|
+
last = blocks.last
|
42
|
+
if last && (block[:source][:begin] == last[:source][:end] + 1) && (block[:target][:begin] == last[:target][:end] + 1)
|
43
|
+
last[:source][:end] = block[:source][:end]
|
44
|
+
last[:target][:end] = block[:target][:end]
|
43
45
|
else
|
44
|
-
|
46
|
+
blocks << block.merge(alignment: :block, delta: block[:target][:begin] - block[:source][:begin])
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
48
|
-
# pp
|
50
|
+
# pp blocks
|
49
51
|
# puts "-----"
|
50
52
|
# puts
|
51
|
-
#
|
53
|
+
# exit
|
54
|
+
# blocks.each do |b|
|
52
55
|
# p [b[:source], b[:target]]
|
53
56
|
# puts "---"
|
54
57
|
# puts str1[b[:source][:begin] ... b[:source][:end]]
|
@@ -60,114 +63,202 @@ class TextAlignment::TextAlignment
|
|
60
63
|
# puts "-=-=-=-=-"
|
61
64
|
# puts
|
62
65
|
|
63
|
-
##
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
if mblocks[0][:source][:begin] > 0
|
69
|
-
e1 = mblocks[0][:source][:begin]
|
70
|
-
e2 = mblocks[0][:target][:begin]
|
66
|
+
## to fill the gaps
|
67
|
+
last_block = nil
|
68
|
+
blocks2 = blocks.inject([]) do |sum, block|
|
69
|
+
b1 = last_block ? last_block[:source][:end] : 0
|
70
|
+
e1 = block[:source][:begin]
|
71
71
|
|
72
|
-
if
|
73
|
-
|
72
|
+
sum += if b1 == e1
|
73
|
+
[block]
|
74
74
|
else
|
75
|
-
|
76
|
-
|
75
|
+
b2 = last_block ? last_block[:target][:end] : 0
|
76
|
+
e2 = block[:target][:begin]
|
77
|
+
|
78
|
+
if b2 == e2
|
79
|
+
[
|
80
|
+
{source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty},
|
81
|
+
block
|
82
|
+
]
|
83
|
+
else
|
84
|
+
if b1 == 0 && b2 == 0
|
85
|
+
len_buffer = (e1 * (1 + TextAlignment::BUFFER_RATE)).to_i + TextAlignment::BUFFER_MIN
|
86
|
+
b2 = e2 - len_buffer if e2 > len_buffer
|
87
|
+
end
|
88
|
+
|
89
|
+
_str1 = str1[b1 ... e1]
|
90
|
+
_str2 = str2[b2 ... e2]
|
77
91
|
|
78
|
-
|
79
|
-
|
80
|
-
|
92
|
+
if _str1.strip.empty? || _str2.strip.empty?
|
93
|
+
[
|
94
|
+
{source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty},
|
95
|
+
block
|
96
|
+
]
|
81
97
|
else
|
82
|
-
|
83
|
-
len_buffer = (len_min * (1 + TextAlignment::BUFFER_RATE)).to_i + TextAlignment::BUFFER_MIN
|
84
|
-
b1 = _str1.length < len_buffer ? 0 : e1 - len_buffer
|
85
|
-
b2 = _str2.length < len_buffer ? 0 : e2 - len_buffer
|
86
|
-
|
87
|
-
@block_alignment[:blocks] << {source:{begin:0, end:b1}, target:{begin:0, end:b2}, alignment: :empty} if b1 > 0
|
88
|
-
|
89
|
-
_str1 = str1[b1 ... e1]
|
90
|
-
_str2 = str2[b2 ... e2]
|
91
|
-
alignment = TextAlignment::MixedAlignment.new(_str1.downcase, _str2.downcase)
|
92
|
-
if alignment.similarity < 0.5
|
93
|
-
@block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:0, end:e2}, alignment: :empty, similarity: alignment.similarity}
|
94
|
-
else
|
95
|
-
@block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:0, end:e2}, alignment:alignment, similarity: alignment.similarity}
|
96
|
-
end
|
98
|
+
local_alignment_blocks(str1, b1, e1, str2, b2, e2, denotations) << block
|
97
99
|
end
|
98
100
|
end
|
99
101
|
end
|
102
|
+
|
103
|
+
last_block = block
|
104
|
+
sum
|
100
105
|
end
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
if
|
112
|
-
|
106
|
+
|
107
|
+
# the last step
|
108
|
+
blocks2 += if last_block.nil?
|
109
|
+
local_alignment_blocks(str1, 0, str1.length, str2, 0, str2.length, denotations)
|
110
|
+
else
|
111
|
+
b1 = last_block[:source][:end]
|
112
|
+
if b1 < str1.length
|
113
|
+
e1 = str1.length
|
114
|
+
|
115
|
+
b2 = last_block[:target][:end]
|
116
|
+
if b2 < str2.length
|
117
|
+
len_buffer = ((e1 - b1) * (1 + TextAlignment::BUFFER_RATE)).to_i + TextAlignment::BUFFER_MIN
|
118
|
+
e2 = (str2.length - b2) > len_buffer ? b2 + len_buffer : str2.length
|
119
|
+
local_alignment_blocks(str1, b1, e1, str2, b2, e2, denotations)
|
113
120
|
else
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
121
|
+
[{source:{begin:last_block[:source][:end], end:str1.length}, alignment: :empty}]
|
122
|
+
end
|
123
|
+
else
|
124
|
+
[]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
@block_alignment[:blocks] = blocks2
|
129
|
+
end
|
130
|
+
|
131
|
+
def local_alignment_blocks(str1, b1, e1, str2, b2, e2, denotations = nil)
|
132
|
+
block2 = str2[b2 ... e2]
|
133
|
+
|
134
|
+
## term-based alignment
|
135
|
+
tblocks = if denotations
|
136
|
+
ds_in_scope = denotations.select{|d| d[:span][:begin] >= b1 && d[:span][:end] <= e1}.
|
137
|
+
sort{|d1, d2| d1[:span][:begin] <=> d2[:span][:begin] || d2[:span][:end] <=> d1[:span][:end] }.
|
138
|
+
map{|d| d.merge(lex:str1[d[:span][:begin] ... d[:span][:end]])}
|
139
|
+
|
140
|
+
position = 0
|
141
|
+
tblocks = ds_in_scope.map do |term|
|
142
|
+
lex = term[:lex]
|
143
|
+
r = block2.index(lex, position)
|
144
|
+
if r.nil?
|
145
|
+
position = nil
|
146
|
+
break
|
147
|
+
end
|
148
|
+
position = r + lex.length
|
149
|
+
{source:term[:span], target:{begin:r + b2, end:r + b2 + lex.length}, alignment: :term, delta: r - term[:span][:begin]}
|
150
|
+
end
|
151
|
+
|
152
|
+
# missing term found
|
153
|
+
tblocks = [] if position.nil?
|
154
|
+
|
155
|
+
# redundant matching found
|
156
|
+
unless position.nil?
|
157
|
+
ds_in_scope.each do |term|
|
158
|
+
lex = term[:lex]
|
159
|
+
look_forward = block2.index(lex, position)
|
160
|
+
unless look_forward.nil?
|
161
|
+
puts lex
|
162
|
+
tblocks = []
|
163
|
+
break
|
119
164
|
end
|
120
165
|
end
|
121
166
|
end
|
122
|
-
|
167
|
+
|
168
|
+
tblocks
|
123
169
|
end
|
124
170
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
171
|
+
if tblocks.empty?
|
172
|
+
if b1 == 0 && e1 == str1.length
|
173
|
+
if (e1 > 2000) || (e2 > 2000)
|
174
|
+
[{source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty}]
|
175
|
+
else
|
176
|
+
block1 = str1[b1 ... e1]
|
177
|
+
block2 = str2[b2 ... e2]
|
131
178
|
|
132
|
-
|
133
|
-
|
134
|
-
|
179
|
+
## character-based alignment
|
180
|
+
alignment = TextAlignment::MixedAlignment.new(block1.downcase, block2.downcase)
|
181
|
+
if alignment.sdiff.nil?
|
182
|
+
[{source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty}]
|
183
|
+
else
|
184
|
+
[{source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: alignment, similarity: alignment.similarity}]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
else
|
188
|
+
block1 = str1[b1 ... e1]
|
189
|
+
block2 = str2[b2 ... e2]
|
190
|
+
|
191
|
+
## character-based alignment
|
192
|
+
alignment = TextAlignment::MixedAlignment.new(block1.downcase, block2.downcase)
|
193
|
+
if alignment.sdiff.nil?
|
194
|
+
[{source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty}]
|
135
195
|
else
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
196
|
+
[{source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: alignment, similarity: alignment.similarity}]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
else
|
200
|
+
last_tblock = nil
|
201
|
+
lblocks = tblocks.inject([]) do |sum, tblock|
|
202
|
+
tb1 = last_tblock ? last_tblock[:source][:end] : b1
|
203
|
+
te1 = tblock[:source][:begin]
|
142
204
|
|
143
|
-
|
144
|
-
|
145
|
-
|
205
|
+
sum += if te1 == tb1
|
206
|
+
[tblock]
|
207
|
+
else
|
208
|
+
tb2 = last_tblock ? last_tblock[:target][:end] : b2
|
209
|
+
te2 = tblock[:target][:begin]
|
210
|
+
|
211
|
+
if b2 == e2
|
212
|
+
[
|
213
|
+
{source:{begin:tb1, end:te1}, alignment: :empty},
|
214
|
+
tblock
|
215
|
+
]
|
146
216
|
else
|
147
|
-
|
217
|
+
[
|
218
|
+
{source:{begin:tb1, end:te1}, target:{begin:tb2, end:te2}, alignment: :empty},
|
219
|
+
tblock
|
220
|
+
]
|
148
221
|
end
|
222
|
+
end
|
149
223
|
|
150
|
-
|
224
|
+
last_tblock = tblock
|
225
|
+
sum
|
226
|
+
end
|
227
|
+
|
228
|
+
if last_tblock[:source][:end] < e1
|
229
|
+
if last_tblock[:target][:end] < e2
|
230
|
+
lblocks << {source:{begin:last_tblock[:source][:end], end:e1}, target:{begin:last_tblock[:target][:end], end:e2}, alignment: :empty}
|
231
|
+
else
|
232
|
+
lblocks << {source:{begin:last_tblock[:source][:end], end:e1}, alignment: :empty}
|
151
233
|
end
|
152
234
|
end
|
153
|
-
end
|
154
235
|
|
155
|
-
|
156
|
-
a[:delta] = a[:target][:begin] - a[:source][:begin]
|
236
|
+
lblocks
|
157
237
|
end
|
158
238
|
end
|
159
239
|
|
240
|
+
|
241
|
+
def indices(str, target)
|
242
|
+
position = 0
|
243
|
+
len = target.len
|
244
|
+
Enumerator.new do |yielder|
|
245
|
+
while idx = str.index(target, position)
|
246
|
+
yielder << idx
|
247
|
+
position = idx + len
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
160
252
|
def transform_begin_position(begin_position)
|
161
253
|
i = @block_alignment[:blocks].index{|b| b[:source][:end] > begin_position}
|
162
254
|
block = @block_alignment[:blocks][i]
|
163
255
|
|
164
|
-
b = if block[:alignment] == :block
|
256
|
+
b = if block[:alignment] == :block || block[:alignment] == :term
|
165
257
|
begin_position + block[:delta]
|
166
258
|
elsif block[:alignment] == :empty
|
167
259
|
if begin_position == block[:source][:begin]
|
168
260
|
block[:target][:begin]
|
169
261
|
else
|
170
|
-
# raise "lost annotation"
|
171
262
|
nil
|
172
263
|
end
|
173
264
|
else
|
@@ -180,13 +271,12 @@ class TextAlignment::TextAlignment
|
|
180
271
|
i = @block_alignment[:blocks].index{|b| b[:source][:end] >= end_position}
|
181
272
|
block = @block_alignment[:blocks][i]
|
182
273
|
|
183
|
-
e = if block[:alignment] == :block
|
274
|
+
e = if block[:alignment] == :block || block[:alignment] == :term
|
184
275
|
end_position + block[:delta]
|
185
276
|
elsif block[:alignment] == :empty
|
186
277
|
if end_position == block[:source][:end]
|
187
278
|
block[:target][:end]
|
188
279
|
else
|
189
|
-
# raise "lost annotation"
|
190
280
|
nil
|
191
281
|
end
|
192
282
|
else
|
@@ -208,14 +298,14 @@ class TextAlignment::TextAlignment
|
|
208
298
|
@lost_annotations = []
|
209
299
|
|
210
300
|
denotations.each do |d|
|
211
|
-
begin
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
end
|
301
|
+
source = {begin:d.begin, end:d.end}
|
302
|
+
d.begin = transform_begin_position(d.begin);
|
303
|
+
d.end = transform_end_position(d.end);
|
304
|
+
raise "invalid transform" unless !d.begin.nil? && !d.end.nil? && d.begin >= 0 && d.end > d.begin && d.end <= @str2.length
|
305
|
+
rescue
|
306
|
+
@lost_annotations << {source: source, target:{begin:d.begin, end:d.end}}
|
307
|
+
d.begin = nil
|
308
|
+
d.end = nil
|
219
309
|
end
|
220
310
|
|
221
311
|
@lost_annotations
|
@@ -226,12 +316,12 @@ class TextAlignment::TextAlignment
|
|
226
316
|
@lost_annotations = []
|
227
317
|
|
228
318
|
r = hdenotations.collect do |d|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
319
|
+
t = transform_a_span(d[:span])
|
320
|
+
raise "invalid transform" unless !t[:begin].nil? && !t[:end].nil? && t[:begin] >= 0 && t[:end] > t[:begin] && t[:end] <= @str2.length
|
321
|
+
new_d = d.dup.merge({span:t})
|
322
|
+
rescue
|
323
|
+
@lost_annotations << {source: d[:span], target:t}
|
324
|
+
nil
|
235
325
|
end.compact
|
236
326
|
|
237
327
|
r
|
@@ -245,7 +335,10 @@ class TextAlignment::TextAlignment
|
|
245
335
|
@block_alignment[:blocks].each do |a|
|
246
336
|
show += case a[:alignment]
|
247
337
|
when :block
|
248
|
-
"===== common ===== [#{a[:source][:begin]} - #{a[:source][:end]}] [#{a[:target][:begin]} - #{a[:target][:end]}]\n" +
|
338
|
+
"===== common (block) ===== [#{a[:source][:begin]} - #{a[:source][:end]}] [#{a[:target][:begin]} - #{a[:target][:end]}]\n" +
|
339
|
+
stext[a[:source][:begin] ... a[:source][:end]] + "\n\n"
|
340
|
+
when :term
|
341
|
+
"===== common (term) ===== [#{a[:source][:begin]} - #{a[:source][:end]}] [#{a[:target][:begin]} - #{a[:target][:end]}]\n" +
|
249
342
|
stext[a[:source][:begin] ... a[:source][:end]] + "\n\n"
|
250
343
|
when :empty
|
251
344
|
"xxxxx disparate texts (similarity: #{a[:similarity]})\n" +
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: text_alignment
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jin-Dong Kim
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-10-
|
11
|
+
date: 2020-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-dictionary
|