text_alignment 0.5 → 0.6.2
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 +19 -15
- data/lib/text_alignment/lcs_comparison.rb +3 -1
- data/lib/text_alignment/text_alignment.rb +85 -134
- 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: c4b2cdf0c257b74c6bec90b93d1907787f3c102108046731c2755684a1b156e9
|
4
|
+
data.tar.gz: 85334dad09a046432503183e3d3ad83841612299038f2f2dac1f9d5d208e1939
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9272bdd6c56717b53d39b3f2009259accb608ea86b99758b6a7ee9cee1e7b275330db55af4e0eba1eba80ee69275a21a3179243394d24139b3018996f659abe1
|
7
|
+
data.tar.gz: a6a9d97d2bf81ac0c2972fd6e9d5202116156d8ff2e5e81a9bf0306e313dbc601522f887bcbcebff8b9d888cc06826a8ce69ba908dce29fa8decad85d53008af
|
data/bin/align_annotations
CHANGED
@@ -35,6 +35,10 @@ def align_mdoc(source_annotations, target_annotations)
|
|
35
35
|
source_annotations.each do |annotations|
|
36
36
|
alignment = TextAlignment::TextAlignment.new(annotations[:text], target_annotations[:text])
|
37
37
|
|
38
|
+
puts alignment.alignment_show
|
39
|
+
puts "-----"
|
40
|
+
puts
|
41
|
+
|
38
42
|
# alignment.block_alignments.each do |a|
|
39
43
|
# p {source:a[:source], target:a[:target]}
|
40
44
|
# puts "--"
|
@@ -133,26 +137,26 @@ else
|
|
133
137
|
|
134
138
|
source_text = source_annotations[:text]
|
135
139
|
|
136
|
-
|
137
|
-
puts alignment.
|
138
|
-
|
140
|
+
puts "[block alignment]"
|
141
|
+
puts alignment.alignment_show
|
142
|
+
puts "====="
|
139
143
|
# exit
|
140
144
|
|
141
145
|
# verification of source denotations
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
148
152
|
|
149
153
|
denotations = alignment.transform_hdenotations(source_annotations[:denotations])
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
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
|
156
160
|
|
157
161
|
lost_annotations += alignment.lost_annotations if alignment.lost_annotations
|
158
162
|
|
@@ -33,7 +33,9 @@ class TextAlignment::LCSComparison
|
|
33
33
|
@str2_match_initial = sdiff[match_initial].new_position
|
34
34
|
@str1_match_final = sdiff[match_final].old_position
|
35
35
|
@str2_match_final = sdiff[match_final].new_position
|
36
|
-
|
36
|
+
mlcs = sdiff.count{|d| d.action == '=' && d.old_element =~ /\S/ && d.new_element =~ /\S/}
|
37
|
+
@similarity = 2 * mlcs / (str1[@str1_match_initial .. @str1_match_final].scan(/\S/).count + str2[@str2_match_initial .. @str2_match_final].scan(/\S/).count).to_f
|
38
|
+
# @similarity = 2 * lcs / (str1[@str1_match_initial .. @str1_match_final].length + str2[@str2_match_initial .. @str2_match_final].length).to_f
|
37
39
|
else
|
38
40
|
@str1_match_initial = 0
|
39
41
|
@str2_match_initial = 0
|
@@ -8,30 +8,29 @@ module TextAlignment; end unless defined? TextAlignment
|
|
8
8
|
TextAlignment::PADDING_LETTERS = ['@', '^', '|', '#', '$', '%', '&', '_'] unless defined? TextAlignment::PADDING_LETTERS
|
9
9
|
|
10
10
|
class TextAlignment::TextAlignment
|
11
|
-
attr_reader :
|
11
|
+
attr_reader :block_alignment
|
12
12
|
attr_reader :similarity
|
13
13
|
attr_reader :lost_annotations
|
14
14
|
|
15
15
|
def initialize(_str1, _str2, _size_ngram = nil, _size_window = nil, _text_similiarity_threshold = nil)
|
16
16
|
raise ArgumentError, "nil string" if _str1.nil? || _str2.nil?
|
17
17
|
|
18
|
-
@
|
19
|
-
@ostr2 = _str2
|
18
|
+
@block_alignment = {source_text:_str1, target_text:_str2}
|
20
19
|
|
21
20
|
str1, str2, mappings = string_preprocessing(_str1, _str2)
|
22
21
|
|
23
22
|
# try exact match
|
24
23
|
block_begin = str2.index(str1)
|
25
24
|
unless block_begin.nil?
|
26
|
-
@
|
27
|
-
return @
|
25
|
+
@block_alignment[:blocks] = [{source:{begin:0, end:str1.length}, target:{begin:block_begin, end:block_begin + str1.length}, delta:block_begin, alignment: :block}]
|
26
|
+
return @block_alignment
|
28
27
|
end
|
29
28
|
|
30
29
|
# try exact match
|
31
30
|
block_begin = str2.downcase.index(str1.downcase)
|
32
31
|
unless block_begin.nil?
|
33
|
-
@
|
34
|
-
return @
|
32
|
+
@block_alignment[:blocks] = [{source:{begin:0, end:str1.length}, target:{begin:block_begin, end:block_begin + str1.length}, delta:block_begin, alignment: :block}]
|
33
|
+
return @block_alignment
|
35
34
|
end
|
36
35
|
|
37
36
|
anchor_finder = TextAlignment::AnchorFinder.new(str1, str2, _size_ngram, _size_window, _text_similiarity_threshold)
|
@@ -64,7 +63,7 @@ class TextAlignment::TextAlignment
|
|
64
63
|
# puts
|
65
64
|
|
66
65
|
## To find block alignments
|
67
|
-
@
|
66
|
+
@block_alignment[:blocks] = []
|
68
67
|
return if mblocks.empty?
|
69
68
|
|
70
69
|
# Initial step
|
@@ -73,35 +72,36 @@ class TextAlignment::TextAlignment
|
|
73
72
|
e2 = mblocks[0][:target][:begin]
|
74
73
|
|
75
74
|
if mblocks[0][:target][:begin] == 0
|
76
|
-
@
|
75
|
+
@block_alignment[:blocks] << {source:{begin:0, end:e1}, target:{begin:0, end:0}, alignment: :empty}
|
77
76
|
else
|
78
77
|
_str1 = str1[0 ... e1]
|
79
78
|
_str2 = str2[0 ... e2]
|
80
79
|
|
81
80
|
unless _str1.strip.empty?
|
82
81
|
if _str2.strip.empty?
|
83
|
-
@
|
82
|
+
@block_alignment[:blocks] << {source:{begin:0, end:e1}, target:{begin:0, end:e2}, alignment: :empty}
|
84
83
|
else
|
85
84
|
len_min = [_str1.length, _str2.length].min
|
86
85
|
len_buffer = (len_min * (1 + TextAlignment::BUFFER_RATE)).to_i + TextAlignment::BUFFER_MIN
|
87
86
|
b1 = _str1.length < len_buffer ? 0 : e1 - len_buffer
|
88
87
|
b2 = _str2.length < len_buffer ? 0 : e2 - len_buffer
|
89
88
|
|
90
|
-
@
|
89
|
+
@block_alignment[:blocks] << {source:{begin:0, end:b1}, target:{begin:0, end:b2}, alignment: :empty} if b1 > 0
|
91
90
|
|
92
91
|
_str1 = str1[b1 ... e1]
|
93
92
|
_str2 = str2[b2 ... e2]
|
94
93
|
alignment = TextAlignment::MixedAlignment.new(_str1.downcase, _str2.downcase, mappings)
|
95
|
-
|
96
|
-
|
94
|
+
similarity = alignment_similarity(_str1, _str2, alignment)
|
95
|
+
if similarity < 0.6
|
96
|
+
@block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:0, end:e2}, alignment: :empty, similarity: similarity}
|
97
97
|
else
|
98
|
-
@
|
98
|
+
@block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:0, end:e2}, alignment:alignment}
|
99
99
|
end
|
100
100
|
end
|
101
101
|
end
|
102
102
|
end
|
103
103
|
end
|
104
|
-
@
|
104
|
+
@block_alignment[:blocks] << mblocks[0].merge(alignment: :block)
|
105
105
|
|
106
106
|
(1 ... mblocks.length).each do |i|
|
107
107
|
b1 = mblocks[i - 1][:source][:end]
|
@@ -112,17 +112,18 @@ class TextAlignment::TextAlignment
|
|
112
112
|
_str2 = str2[b2 ... e2]
|
113
113
|
unless _str1.strip.empty?
|
114
114
|
if _str2.strip.empty?
|
115
|
-
@
|
115
|
+
@block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty}
|
116
116
|
else
|
117
117
|
alignment = TextAlignment::MixedAlignment.new(_str1.downcase, _str2.downcase, mappings)
|
118
|
-
|
119
|
-
|
118
|
+
similarity = alignment_similarity(_str1, _str2, alignment)
|
119
|
+
if similarity < 0.6
|
120
|
+
@block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty, similarity: similarity}
|
120
121
|
else
|
121
|
-
@
|
122
|
+
@block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment:alignment}
|
122
123
|
end
|
123
124
|
end
|
124
125
|
end
|
125
|
-
@
|
126
|
+
@block_alignment[:blocks] << mblocks[i].merge(alignment: :block)
|
126
127
|
end
|
127
128
|
|
128
129
|
# Final step
|
@@ -134,7 +135,7 @@ class TextAlignment::TextAlignment
|
|
134
135
|
|
135
136
|
unless _str1.strip.empty?
|
136
137
|
if _str2.strip.empty?
|
137
|
-
@
|
138
|
+
@block_alignment[:blocks] << {source:{begin:b1, end:str1.length}, target:{begin:b2, end:str2.length}, alignment: :empty}
|
138
139
|
else
|
139
140
|
len_min = [_str1.length, _str2.length].min
|
140
141
|
len_buffer = (len_min * (1 + TextAlignment::BUFFER_RATE)).to_i + TextAlignment::BUFFER_MIN
|
@@ -144,57 +145,58 @@ class TextAlignment::TextAlignment
|
|
144
145
|
_str2 = str2[b2 ... e2]
|
145
146
|
|
146
147
|
alignment = TextAlignment::MixedAlignment.new(_str1.downcase, _str2.downcase, mappings)
|
147
|
-
|
148
|
-
|
148
|
+
similarity = alignment_similarity(_str1, _str2, alignment)
|
149
|
+
if similarity < 0.6
|
150
|
+
@block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty, similarity: similarity}
|
149
151
|
else
|
150
|
-
@
|
152
|
+
@block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment:alignment}
|
151
153
|
end
|
152
154
|
|
153
|
-
@
|
155
|
+
@block_alignment[:blocks] << {source:{begin:e1, end:-1}, target:{begin:e2, end:-1}, alignment: :empty} if e1 < str1.length
|
154
156
|
end
|
155
157
|
end
|
156
158
|
end
|
157
159
|
|
158
|
-
@
|
160
|
+
@block_alignment[:blocks].each do |a|
|
159
161
|
a[:delta] = a[:target][:begin] - a[:source][:begin]
|
160
162
|
end
|
161
163
|
end
|
162
164
|
|
163
165
|
def transform_begin_position(begin_position)
|
164
|
-
i = @
|
165
|
-
|
166
|
-
|
167
|
-
b = if
|
168
|
-
begin_position +
|
169
|
-
elsif
|
170
|
-
if begin_position ==
|
171
|
-
|
166
|
+
i = @block_alignment[:blocks].index{|b| b[:source][:end] > begin_position}
|
167
|
+
block = @block_alignment[:blocks][i]
|
168
|
+
|
169
|
+
b = if block[:alignment] == :block
|
170
|
+
begin_position + block[:delta]
|
171
|
+
elsif block[:alignment] == :empty
|
172
|
+
if begin_position == block[:source][:begin]
|
173
|
+
block[:target][:begin]
|
172
174
|
else
|
173
175
|
# raise "lost annotation"
|
174
176
|
nil
|
175
177
|
end
|
176
178
|
else
|
177
|
-
r =
|
178
|
-
r.nil? ? nil : r +
|
179
|
+
r = block[:alignment].transform_begin_position(begin_position - block[:source][:begin])
|
180
|
+
r.nil? ? nil : r + block[:target][:begin]
|
179
181
|
end
|
180
182
|
end
|
181
183
|
|
182
184
|
def transform_end_position(end_position)
|
183
|
-
i = @
|
184
|
-
|
185
|
-
|
186
|
-
e = if
|
187
|
-
end_position +
|
188
|
-
elsif
|
189
|
-
if end_position ==
|
190
|
-
|
185
|
+
i = @block_alignment[:blocks].index{|b| b[:source][:end] >= end_position}
|
186
|
+
block = @block_alignment[:blocks][i]
|
187
|
+
|
188
|
+
e = if block[:alignment] == :block
|
189
|
+
end_position + block[:delta]
|
190
|
+
elsif block[:alignment] == :empty
|
191
|
+
if end_position == block[:source][:end]
|
192
|
+
block[:target][:end]
|
191
193
|
else
|
192
194
|
# raise "lost annotation"
|
193
195
|
nil
|
194
196
|
end
|
195
197
|
else
|
196
|
-
r =
|
197
|
-
r.nil? ? nil : r +
|
198
|
+
r = block[:alignment].transform_end_position(end_position - block[:source][:begin])
|
199
|
+
r.nil? ? nil : r + block[:target][:begin]
|
198
200
|
end
|
199
201
|
end
|
200
202
|
|
@@ -240,87 +242,23 @@ class TextAlignment::TextAlignment
|
|
240
242
|
r
|
241
243
|
end
|
242
244
|
|
243
|
-
def alignment_table
|
244
|
-
table = <<-TABLE
|
245
|
-
<table class='text_alignment_table'>
|
246
|
-
<thead>
|
247
|
-
<tr>
|
248
|
-
<th class='text_alignment_left' style='width:50%'>Text 1</th>
|
249
|
-
<th class='text_alignment_rigt'>Text 2</th>
|
250
|
-
</tr>
|
251
|
-
</thead>
|
252
|
-
<tbody>
|
253
|
-
TABLE
|
254
|
-
|
255
|
-
@block_alignments.each do |a|
|
256
|
-
table += alignment_table_th(a)
|
257
|
-
table += "<tr>\n" + case a[:alignment]
|
258
|
-
when :block
|
259
|
-
"<td colspan='2' class='text_alignment_common'>" +
|
260
|
-
@ostr1[a[:source][:begin] ... a[:source][:end]] +
|
261
|
-
"</td>\n"
|
262
|
-
when :empty
|
263
|
-
"<td class='text_alignment_left'>" + @ostr1[a[:source][:begin] ... a[:source][:end]] + "</td>\n" +
|
264
|
-
"<td class='text_alignment_right'>" + @ostr2[a[:target][:begin] ... a[:target][:end]] + "</td>\n"
|
265
|
-
else
|
266
|
-
base = a[:source][:begin]
|
267
|
-
astr1 = a[:alignment].sdiff.map do |c|
|
268
|
-
case c.action
|
269
|
-
when '='
|
270
|
-
@ostr1[c.old_position + base]
|
271
|
-
when '+'
|
272
|
-
'_'
|
273
|
-
when '-'
|
274
|
-
@ostr1[c.old_position + base]
|
275
|
-
when '!'
|
276
|
-
@ostr1[c.old_position + base] + '_'
|
277
|
-
end
|
278
|
-
end.join('')
|
279
|
-
|
280
|
-
base = a[:target][:begin]
|
281
|
-
astr2 = a[:alignment].sdiff.map do |c|
|
282
|
-
case c.action
|
283
|
-
when '='
|
284
|
-
@ostr2[c.new_position + base]
|
285
|
-
when '+'
|
286
|
-
@ostr2[c.new_position + base]
|
287
|
-
when '-'
|
288
|
-
'_'
|
289
|
-
when '!'
|
290
|
-
'_' + @ostr2[c.new_position + base]
|
291
|
-
end
|
292
|
-
end.join('')
|
293
|
-
|
294
|
-
"<td class='text_alignment_left'>" + astr1 + "</td>\n" +
|
295
|
-
"<td class='text_alignment_right'>" + astr2 + "</td>\n"
|
296
|
-
end + "</tr>\n"
|
297
|
-
end
|
298
|
-
table += '</tbody></table>'
|
299
|
-
end
|
300
|
-
|
301
|
-
def alignment_table_th(a)
|
302
|
-
"<tr>" +
|
303
|
-
"<th class='text_alignment_left'>#{a[:source][:begin]} - #{a[:source][:end]}</th>" +
|
304
|
-
"<th class='text_alignment_right'>#{a[:target][:begin]} - #{a[:target][:end]}</th>" +
|
305
|
-
"</tr>"
|
306
|
-
end
|
307
|
-
|
308
245
|
def alignment_show
|
246
|
+
stext = @block_alignment[:source_text]
|
247
|
+
ttext = @block_alignment[:target_text]
|
248
|
+
|
309
249
|
show = ''
|
310
|
-
@
|
250
|
+
@block_alignment[:blocks].each do |a|
|
311
251
|
show += case a[:alignment]
|
312
252
|
when :block
|
313
253
|
"===== common =====\n" +
|
314
|
-
|
254
|
+
stext[a[:source][:begin] ... a[:source][:end]] + "\n\n"
|
315
255
|
when :empty
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
puts
|
256
|
+
"xxxxx disparate texts (similarity: #{a[:similarity]})\n" +
|
257
|
+
"<<<<< string 1\n" +
|
258
|
+
stext[a[:source][:begin] ... a[:source][:end]] + "\n\n" +
|
259
|
+
">>>>> string 2\n" +
|
260
|
+
ttext[a[:target][:begin] ... a[:target][:end]] + "\n\n"
|
322
261
|
else
|
323
|
-
puts "***** local mismatch"
|
324
262
|
astr1 = ''
|
325
263
|
astr2 = ''
|
326
264
|
|
@@ -328,13 +266,13 @@ class TextAlignment::TextAlignment
|
|
328
266
|
astr1 = a[:alignment].sdiff.map do |c|
|
329
267
|
case c.action
|
330
268
|
when '='
|
331
|
-
|
269
|
+
stext[c.old_position + base]
|
332
270
|
when '+'
|
333
271
|
'_'
|
334
272
|
when '-'
|
335
|
-
|
273
|
+
stext[c.old_position + base]
|
336
274
|
when '!'
|
337
|
-
|
275
|
+
stext[c.old_position + base] + '_'
|
338
276
|
end
|
339
277
|
end.join('')
|
340
278
|
|
@@ -342,21 +280,22 @@ class TextAlignment::TextAlignment
|
|
342
280
|
astr2 = a[:alignment].sdiff.map do |c|
|
343
281
|
case c.action
|
344
282
|
when '='
|
345
|
-
|
283
|
+
ttext[c.new_position + base]
|
346
284
|
when '+'
|
347
|
-
|
285
|
+
ttext[c.new_position + base]
|
348
286
|
when '-'
|
349
287
|
'_'
|
350
288
|
when '!'
|
351
|
-
'_' +
|
289
|
+
'_' + ttext[c.new_position + base]
|
352
290
|
end
|
353
291
|
end.join('')
|
354
292
|
|
355
|
-
|
356
|
-
|
357
|
-
|
293
|
+
"***** local mismatch\n" +
|
294
|
+
"[#{astr1}]\n" +
|
295
|
+
"[#{astr2}]\n\n"
|
358
296
|
end
|
359
297
|
end
|
298
|
+
show
|
360
299
|
end
|
361
300
|
|
362
301
|
private
|
@@ -381,15 +320,15 @@ class TextAlignment::TextAlignment
|
|
381
320
|
pletters = TextAlignment::PADDING_LETTERS
|
382
321
|
|
383
322
|
# find the padding letter for str1
|
384
|
-
padding_letter1 = begin
|
323
|
+
@padding_letter1 = begin
|
385
324
|
i = pletters.index{|l| str2.index(l).nil?}
|
386
325
|
raise RuntimeError, "Could not find a padding letter for str1" if i.nil?
|
387
326
|
TextAlignment::PADDING_LETTERS[i]
|
388
327
|
end
|
389
328
|
|
390
329
|
# find the padding letter for str2
|
391
|
-
padding_letter2 = begin
|
392
|
-
i = pletters.index{|l| l != padding_letter1 && str1.index(l).nil?}
|
330
|
+
@padding_letter2 = begin
|
331
|
+
i = pletters.index{|l| l != @padding_letter1 && str1.index(l).nil?}
|
393
332
|
raise RuntimeError, "Could not find a padding letter for str2" if i.nil?
|
394
333
|
TextAlignment::PADDING_LETTERS[i]
|
395
334
|
end
|
@@ -400,12 +339,12 @@ class TextAlignment::TextAlignment
|
|
400
339
|
from = f[1]
|
401
340
|
|
402
341
|
if str2.index(f[0])
|
403
|
-
to = f[0] + (padding_letter1 * (f[1].length - 1))
|
342
|
+
to = f[0] + (@padding_letter1 * (f[1].length - 1))
|
404
343
|
str1.gsub!(from, to)
|
405
344
|
end
|
406
345
|
|
407
346
|
if str1.index(f[0])
|
408
|
-
to = f[0] + (padding_letter2 * (f[1].length - 1))
|
347
|
+
to = f[0] + (@padding_letter2 * (f[1].length - 1))
|
409
348
|
str2.gsub!(from, to)
|
410
349
|
end
|
411
350
|
end
|
@@ -414,4 +353,16 @@ class TextAlignment::TextAlignment
|
|
414
353
|
[str1, str2, mappings]
|
415
354
|
end
|
416
355
|
|
356
|
+
def alignment_similarity(_s1, _s2, alignment)
|
357
|
+
return 0 if alignment.sdiff.nil?
|
358
|
+
|
359
|
+
# compute the lcs only with non-whitespace letters
|
360
|
+
lcs = alignment.sdiff.count{|d| d.action == '=' && d.old_element =~ /\S/ && d.new_element =~ /\S/}
|
361
|
+
|
362
|
+
s1 = _s1.tr(@padding_letter1, ' ')
|
363
|
+
s2 = _s2.tr(@padding_letter2, ' ')
|
364
|
+
|
365
|
+
similarity = 2 * lcs / (s1.scan(/\S/).count + s2.scan(/\S/).count).to_f
|
366
|
+
end
|
367
|
+
|
417
368
|
end
|
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:
|
4
|
+
version: 0.6.2
|
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-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-dictionary
|