rubyfca 0.2.11 → 0.3.0

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/lib/rubyfca.rb CHANGED
@@ -1,40 +1,39 @@
1
- #!/usr/bin/env ruby
2
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
3
2
 
4
3
  ## lib/rubyfca.rb -- Formal Concept Analysis tool in Ruby
5
4
  ## Author:: Yoichiro Hasebe (mailto: yohasebe@gmail.com)
6
- ## Kow Kuroda (mailto: kuroda@nict.go.jp)
7
- ## Copyright:: Copyright 2009 Yoichiro Hasebe and Kow Kuroda
5
+ ## Kow Kuroda (mailto: kuroda@nict.go.jp)
6
+ ## Copyright:: Copyright 2009-2023 Yoichiro Hasebe and Kow Kuroda
8
7
  ## License:: GNU GPL version 3
9
8
 
10
- $: << File.dirname(__FILE__) + "/rubyfca"
9
+ require "csv"
10
+ require "optimist"
11
+ require "roo"
11
12
 
12
- require 'csv'
13
- require 'ruby_graphviz'
14
- require 'version'
15
- require 'trollop'
13
+ require_relative "./rubyfca/ruby_graphviz"
14
+ require_relative "./rubyfca/version"
16
15
 
17
16
  private
18
17
 
19
- ## Take two arrays each consisting of 0s and 1s and create their logical disjunction
18
+ ## Take two arrays each consisting of 0s and 1s and create their logical disjunction
20
19
  def create_and_ary(a, b)
21
20
  return false if (s = a.size) != b.size
22
- result = (0..(s-1)).to_a.map{|i| a[i].to_i & b[i].to_i}
23
- return result
21
+
22
+ (0..(s - 1)).to_a.map { |i| a[i].to_i & b[i].to_i }
24
23
  end
25
24
 
26
25
  ## Take two arrays each consisting of 0s and 1s and create their logical conjunction
27
26
  def create_or_ary(a, b)
28
27
  return false if (s = a.size) != b.size
29
- result = (0..(s-1)).to_a.map{|i| a[i].to_i | b[i].to_i}
30
- return result
28
+
29
+ (0..(s - 1)).to_a.map { |i| a[i].to_i | b[i].to_i }
31
30
  end
32
31
 
33
32
  public
34
33
 
35
34
  ## Take care of errors
36
35
  def showerror(sentence, severity)
37
- if severity == 0
36
+ if severity.zero?
38
37
  puts "Warning: #{sentence} The output may not be meaningful."
39
38
  elsif severity == 1
40
39
  puts "Error: #{sentence} No output generated."
@@ -44,26 +43,27 @@ end
44
43
 
45
44
  ## Basic structure of the code is the same as Fcastone written in Perl by Uta Priss
46
45
  class FormalContext
47
-
48
46
  ## Converte cxt data to three basic structures of objects, attributes, and matrix
49
- def initialize(input, mode, label_contraction = false)
50
- if input.size == 0
51
- showerror("File is empty", 1)
52
- end
53
- input.gsub!(" ", "&nbsp;")
47
+ def initialize(input, mode, label_contraction: false)
48
+ showerror("File is empty", 1) if input.empty?
49
+ input = input.gsub(" ", "&nbsp;")
54
50
  begin
55
51
  case mode
56
52
  when /cxt\z/
57
53
  read_cxt(input)
58
54
  when /csv\z/
59
55
  read_csv(input)
56
+ when /xlsx\z/
57
+ read_xlsx(input)
60
58
  end
61
- rescue => e
62
- showerror("Input data contains a syntax problem.", 1)
59
+ rescue StandardError => e
60
+ pp e.message
61
+ pp e.backtrace
62
+ showerror("Input data contains a syntactic problem.", 1)
63
63
  end
64
64
  @label_contraction = label_contraction
65
65
  end
66
-
66
+
67
67
  ## process cxt data
68
68
  def read_cxt(input)
69
69
  lines = input.split
@@ -71,26 +71,41 @@ class FormalContext
71
71
  if (lines[0] !~ /B/i || (2 * lines[1].to_i + lines[2].to_i + t1) != lines.size)
72
72
  showerror("Wrong cxt format!", 1)
73
73
  end
74
- @objects = lines[t1..(lines[1].to_i + t1 - 1)]
75
- @attributes = lines[(lines[1].to_i + t1) .. (lines[1].to_i + lines[2].to_i + t1 - 1)]
76
- lines = lines[(lines[1].to_i + lines[2].to_i + t1) .. lines.size]
77
- @matrix = changecrosssymbol("X", "\\.", lines)
74
+ @objects = lines[t1..(lines[1].to_i + t1 - 1)]
75
+ @attributes = lines[(lines[1].to_i + t1)..(lines[1].to_i + lines[2].to_i + t1 - 1)]
76
+ lines = lines[(lines[1].to_i + lines[2].to_i + t1)..lines.size]
77
+ @matrix = changecrosssymbol("X", "\\.", lines)
78
78
  end
79
-
79
+
80
80
  # process csv data using the standard csv library
81
81
  def read_csv(input)
82
82
  input = remove_blank(input)
83
83
  data = CSV.parse(input)
84
- @objects = trim_ary(data.transpose.first[1..-1])
85
- @attributes = trim_ary(data.first[1..-1])
84
+ @objects = trim_ary(data.transpose.first[1..])
85
+ @attributes = trim_ary(data.first[1..])
86
+ @matrix = []
87
+ data[1..].each do |line|
88
+ @matrix << line[1..].collect { |cell| /x/i =~ cell ? 1 : 0 }
89
+ end
90
+ end
91
+
92
+ # process xlsx data using the roo gem
93
+ def read_xlsx(file_path)
94
+ xlsx = Roo::Spreadsheet.open(file_path, extension: :xlsx)
95
+ @objects = xlsx.sheet(0).column(1)[1..]
96
+ @attributes = xlsx.sheet(0).row(1)[1..]
86
97
  @matrix = []
87
- data[1..-1].each do |line|
88
- @matrix << line[1..-1].collect { |cell| /x/i =~ cell ? 1 : 0 }
98
+ (2..xlsx.sheet(0).last_row).each do |row|
99
+ matrix_row = []
100
+ (2..xlsx.sheet(0).last_column).each do |col|
101
+ matrix_row << (xlsx.sheet(0).cell(row, col) =~ /x/i ? 1 : 0)
102
+ end
103
+ @matrix << matrix_row
89
104
  end
90
105
  end
91
-
106
+
92
107
  def remove_blank(input)
93
- blank_removed = ""
108
+ blank_removed = +""
94
109
  input.split("\n").each do |line|
95
110
  line = line.strip
96
111
  unless /\A\s*\z/ =~ line
@@ -99,26 +114,22 @@ class FormalContext
99
114
  end
100
115
  blank_removed
101
116
  end
102
-
117
+
103
118
  def trim_ary(ary)
104
- newary = ary.collect do |cell|
105
- cell.strip
106
- end
107
- newary
119
+ ary.map(&:strip)
108
120
  end
109
-
121
+
110
122
  ## Apply a formal concept analysis on the matrix
111
- def calcurate
123
+ def calcurate
112
124
  @concepts, @extM, @intM = ganter_alg(@matrix)
113
125
  @relM, @reltrans, @rank = create_rel(@intM)
114
126
  @gammaM, @muM = gammaMu(@extM, @intM, @matrix)
115
127
  end
116
128
 
117
- ## This is an implementation of an algorithm described by Bernhard Ganter
129
+ ## This is an implementation of an algorithm described by Bernhard Ganter
118
130
  ## in "Two basic algorithms in concept analysis." Technische Hochschule
119
131
  ## Darmstadt, FB4-Preprint, 831, 1984.
120
132
  def ganter_alg(matrix)
121
-
122
133
  m = matrix
123
134
 
124
135
  ## all arrays except @idx are arrays of arrays of 0's and 1's
@@ -129,25 +140,25 @@ class FormalContext
129
140
  int_B = []
130
141
  endkey = []
131
142
  temp = []
132
-
143
+
133
144
  ## the level in the lattice from the top
134
- lvl = 0
145
+ lvl = 0
135
146
  ## is lower than the index of leftmost attr
136
- idx[lvl] = -1
147
+ idx[lvl] = -1
137
148
  ## number of attr. and objs
138
- anzM = m.size
139
- ## only needed for initialization
149
+ anzM = m.size
150
+ ## only needed for initialization
140
151
  anzG = m[0].size
141
152
  ## initialize extA[0] = [1,...,1]
142
- anzG.times do |i|
143
- if !ext_A[0]
153
+ anzG.times do
154
+ unless ext_A[0]
144
155
  ext_A[0] = []
145
156
  end
146
157
  ext_A[0] << 1
147
158
  end
148
159
  ## initialize extB[0] = [0,...,0]
149
- anzM.times do |i|
150
- if !int_B[0]
160
+ anzM.times do
161
+ unless int_B[0]
151
162
  int_B[0] = []
152
163
  end
153
164
  int_B[0] << 0
@@ -155,7 +166,7 @@ class FormalContext
155
166
  anzCpt = 0
156
167
  extension[0] = ext_A[0]
157
168
  intension[0] = int_B[0]
158
- anzM.times do |i|
169
+ anzM.times do
159
170
  endkey << 1
160
171
  end
161
172
 
@@ -164,13 +175,11 @@ class FormalContext
164
175
  (anzM - 1).downto(0) do |i|
165
176
  breakkey = false
166
177
  if (int_B[lvl][i] != 1)
167
- while (i < idx[lvl])
168
- lvl -= 1
169
- end
178
+ lvl -= 1 while (i < idx[lvl])
170
179
  idx[lvl + 1] = i
171
180
  ext_A[lvl + 1] = create_and_ary(ext_A[lvl], m[i])
172
181
  0.upto(i - 1) do |j|
173
- if(!breakkey && int_B[lvl][j] != 1)
182
+ if (!breakkey && int_B[lvl][j] != 1)
174
183
  temp = create_and_ary(ext_A[lvl + 1], m[j])
175
184
  if temp == ext_A[lvl + 1]
176
185
  breakkey = true
@@ -180,7 +189,7 @@ class FormalContext
180
189
  unless breakkey
181
190
  int_B[lvl + 1] = int_B[lvl].dup
182
191
  int_B[lvl + 1][i] = 1
183
- (i+1).upto(anzM - 1) do |k|
192
+ (i + 1).upto(anzM - 1) do |k|
184
193
  if int_B[lvl + 1][k] != 1
185
194
  temp = create_and_ary(ext_A[lvl + 1], m[k])
186
195
  if temp == ext_A[lvl + 1]
@@ -194,7 +203,7 @@ class FormalContext
194
203
  intension[anzCpt] = int_B[lvl]
195
204
  break
196
205
  end
197
- end
206
+ end
198
207
  end
199
208
  end
200
209
 
@@ -214,7 +223,7 @@ class FormalContext
214
223
  [c, intension, extension]
215
224
  end
216
225
 
217
- ## Output arrayconsists of the following:
226
+ ## Output arrayconsists of the following:
218
227
  ## r (subconcept superconcept relation)
219
228
  ## rt (trans. closure of r)
220
229
  ## s (ranked concepts)
@@ -231,11 +240,11 @@ class FormalContext
231
240
  unless r[i]
232
241
  r[i] = []
233
242
  end
234
- r[i][j] = 0;
243
+ r[i][j] = 0
235
244
  unless rt[i]
236
245
  rt[i] = []
237
246
  end
238
- rt[i][j] = 0;
247
+ rt[i][j] = 0
239
248
  end
240
249
  end
241
250
 
@@ -265,16 +274,15 @@ class FormalContext
265
274
  s[rank[i]] = []
266
275
  end
267
276
  s[rank[i]] << i
268
- end
277
+ end
269
278
  s = s.collect do |i|
270
- i ? i : [0]
279
+ i || [0]
271
280
  end
272
281
 
273
282
  [r, rt, s]
274
- end
283
+ end
275
284
 
276
285
  def gammaMu(extent, intent, cxt)
277
-
278
286
  gamma = []
279
287
  mu = []
280
288
  invcxt = []
@@ -288,31 +296,31 @@ class FormalContext
288
296
  0.upto(intent.size - 1) do |j|
289
297
  0.upto(cxt.size - 1) do |i|
290
298
  gamma[i] = [] unless gamma[i]
291
- if cxt[i] == intent[j]
292
- gamma[i][j] = 2
293
- elsif (!@label_contraction && create_or_ary(cxt[i], intent[j]) == cxt[i])
294
- gamma[i][j] = 1
295
- else
296
- gamma[i][j] = 0
297
- end
299
+ gamma[i][j] = if cxt[i] == intent[j]
300
+ 2
301
+ elsif (!@label_contraction && create_or_ary(cxt[i], intent[j]) == cxt[i])
302
+ 1
303
+ else
304
+ 0
305
+ end
298
306
  end
299
-
307
+
300
308
  0.upto(invcxt.size - 1) do |i|
301
309
  # next unless invcxt[i]
302
310
  mu[i] = [] unless mu[i]
303
- if invcxt[i] == extent[j]
304
- mu[i][j] = 2
305
- elsif (!@label_contraction && create_or_ary(invcxt[i], extent[j]) == invcxt[i])
306
- mu[i][j] = 1
307
- else
308
- mu[i][j] = 0
309
- end
311
+ mu[i][j] = if invcxt[i] == extent[j]
312
+ 2
313
+ elsif (!@label_contraction && create_or_ary(invcxt[i], extent[j]) == invcxt[i])
314
+ 1
315
+ else
316
+ 0
317
+ end
310
318
  end
311
319
  end
312
-
320
+
313
321
  [gamma, mu]
314
322
  end
315
-
323
+
316
324
  def changecrosssymbol(char1, char2, lns)
317
325
  rel = []
318
326
  lns.each do |ln|
@@ -333,36 +341,36 @@ class FormalContext
333
341
  ## Generate Graphviz dot data (not creating a file)
334
342
  ## For options, see 'rubyfca'
335
343
  def generate_dot(opts)
336
- index_max_width = @concepts.size.to_s.split(//).size
337
- nodesep = opts[:nodesep] ? opts[:nodesep].to_s : "0.4"
338
- ranksep = opts[:ranksep] ? opts[:ranksep].to_s : "0.2"
339
- clattice = RubyGraphviz.new("clattice", :rankdir => "", :nodesep => nodesep, :ranksep => ranksep)
340
-
341
- if opts[:circle] and opts[:legend]
342
- legend = RubyGraphviz.new("legend", :rankdir => "TB", :lebelloc => "t", :centered => "false")
343
- legend.node_default(:shape => "plaintext")
344
- legend.edge_default(:color => "gray60") if opts[:coloring]
344
+ # index_max_width = @concepts.size.to_s.split(//).size
345
+ nodesep = opts[:nodesep] ? opts[:nodesep].to_s : "0.4"
346
+ ranksep = opts[:ranksep] ? opts[:ranksep].to_s : "0.2"
347
+ clattice = RubyGraphviz.new("clattice", rankdir: "", nodesep: nodesep, ranksep: ranksep)
348
+
349
+ if opts[:circle] && opts[:legend]
350
+ legend = RubyGraphviz.new("legend", rankdir: "TB", lebelloc: "t", centered: "false")
351
+ legend.node_default(shape: "plaintext")
352
+ legend.edge_default(color: "gray60") if opts[:coloring]
345
353
  legends = []
346
354
  end
347
355
 
348
356
  if opts[:circle]
349
- clattice.node_default(:shape => "circle", :style => "filled")
350
- clattice.edge_default(:dir => "none", :minlen => "2")
351
- clattice.edge_default(:color => "gray60") if opts[:coloring]
357
+ clattice.node_default(shape: "circle", style: "filled")
358
+ clattice.edge_default(dir: "none", minlen: "2")
359
+ clattice.edge_default(color: "gray60") if opts[:coloring]
352
360
  else
353
- clattice.node_default(:shape => "record", :margin => "0.2,0.055")
354
- clattice.edge_default(:dir => "none")
355
- clattice.edge_default(:color => "gray60") if opts[:coloring]
361
+ clattice.node_default(shape: "record", margin: "0.2,0.055")
362
+ clattice.edge_default(dir: "none")
363
+ clattice.edge_default(color: "gray60") if opts[:coloring]
356
364
  end
357
-
365
+
358
366
  0.upto(@concepts.size - 1) do |i|
359
367
  objfull = []
360
- attrfull = []
368
+ attrfull = []
361
369
  0.upto(@gammaM.size - 1) do |j|
362
370
  if @gammaM[j][i] == 2
363
- # pointing finger does not appear correctly in eps...
371
+ # pointing finger does not appear correctly in eps...
364
372
  # obj = opts[:full] ? @objects[j] + " " + [0x261C].pack("U") : @objects[j]
365
- obj = opts[:full] ? @objects[j] + "*" : @objects[j]
373
+ obj = opts[:full] ? @objects[j].to_s + "*" : @objects[j].to_s
366
374
  objfull << obj
367
375
  elsif @gammaM[j][i] == 1
368
376
  objfull << @objects[j]
@@ -370,9 +378,9 @@ class FormalContext
370
378
  end
371
379
  0.upto(@muM.size - 1) do |k|
372
380
  if @muM[k][i] == 2
373
- # pointing finger does not appear correctly in eps...
381
+ # pointing finger does not appear correctly in eps...
374
382
  # att = opts[:full] ? @attributes[k] + " " + [0x261C].pack("U") : @attributes[k]
375
- att = opts[:full] ? @attributes[k] + "*" : @attributes[k]
383
+ att = opts[:full] ? @attributes[k].to_s + "*" : @attributes[k].to_s
376
384
  attrfull << att
377
385
  elsif @muM[k][i] == 1
378
386
  attrfull << @attributes[k]
@@ -381,21 +389,21 @@ class FormalContext
381
389
 
382
390
  concept_id = i + 1
383
391
 
384
- attr_str = attrfull.join('<br />')
392
+ attr_str = attrfull.join("<br />")
385
393
  attr_str = attr_str == "" ? " " : attr_str
386
-
387
- if opts[:coloring] == 0 or /\A\s+\z/ =~ attr_str
394
+
395
+ if opts[:coloring].zero? || /\A\s+\z/ =~ attr_str
388
396
  attr_color = "white"
389
397
  elsif opts[:coloring] == 1
390
398
  attr_color = "lightblue"
391
399
  elsif opts[:coloring] == 2
392
400
  attr_color = "gray87"
393
401
  end
394
-
395
- obj_str = objfull.join('<br />')
402
+
403
+ obj_str = objfull.join("<br />")
396
404
  obj_str = obj_str == "" ? " " : obj_str
397
405
 
398
- if opts[:coloring] == 0 or /\A\s+\z/ =~ obj_str
406
+ if opts[:coloring].zero? || /\A\s+\z/ =~ obj_str
399
407
  obj_color = "white"
400
408
  elsif opts[:coloring] == 1
401
409
  obj_color = "pink"
@@ -403,37 +411,36 @@ class FormalContext
403
411
  obj_color = "gray92"
404
412
  end
405
413
 
406
- label = "<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">" +
407
- "<tr><td balign=\"left\" align=\"left\" bgcolor=\"#{attr_color}\">#{attr_str}</td></tr>" +
408
- "<tr><td balign=\"left\" align=\"left\" bgcolor=\"#{obj_color}\">#{obj_str}</td></tr>" +
409
- "</table>>"
414
+ label = "<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">" \
415
+ "<tr><td balign=\"left\" align=\"left\" bgcolor=\"#{attr_color}\">#{attr_str}</td></tr>" \
416
+ "<tr><td balign=\"left\" align=\"left\" bgcolor=\"#{obj_color}\">#{obj_str}</td></tr>" \
417
+ "</table>>"
410
418
 
411
- if opts[:circle] and opts[:legend]
419
+ if opts[:circle] && opts[:legend]
412
420
 
413
- leg = "<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">" +
414
- "<tr><td rowspan=\"2\">#{concept_id}</td><td balign=\"left\" align=\"left\" bgcolor=\"#{attr_color}\">#{attr_str}</td></tr>" +
415
- "<tr><td balign=\"left\" align=\"left\" bgcolor=\"#{obj_color}\">#{obj_str}</td></tr>" +
416
- "</table>>"
421
+ leg = "<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">" \
422
+ "<tr><td rowspan=\"2\">#{concept_id}</td><td balign=\"left\" align=\"left\" bgcolor=\"#{attr_color}\">#{attr_str}</td></tr>" \
423
+ "<tr><td balign=\"left\" align=\"left\" bgcolor=\"#{obj_color}\">#{obj_str}</td></tr>" \
424
+ "</table>>"
417
425
 
418
- if !attrfull.empty? or !objfull.empty?
419
- legend.node("cl#{concept_id}k", :label => concept_id, :style => "invis")
420
- legend.node("cl#{concept_id}v", :label => leg, :fillcolor => "white")
421
- legend.rank("cl#{concept_id}k", "cl#{concept_id}v", :style => "invis", :length => "0.0")
426
+ if !attrfull.empty? || !objfull.empty?
427
+ legend.node("cl#{concept_id}k", label: concept_id, style: "invis")
428
+ legend.node("cl#{concept_id}v", label: leg, fillcolor: "white")
429
+ legend.rank("cl#{concept_id}k", "cl#{concept_id}v", style: "invis", length: "0.0")
422
430
  if legends[-1]
423
- legend.edge("cl#{legends[-1]}k", "cl#{concept_id}k", :style => "invis", :length => "0.0")
431
+ legend.edge("cl#{legends[-1]}k", "cl#{concept_id}k", style: "invis", length: "0.0")
424
432
  end
425
433
  legends << concept_id
426
434
  end
427
435
  end
428
-
436
+
429
437
  if opts[:circle]
430
- clattice.node("c#{i}", :width => "0.5", :fontsize => "14.0", :label => concept_id)
438
+ clattice.node("c#{i}", width: "0.5", fontsize: "14.0", label: concept_id)
431
439
  else
432
- clattice.node("c#{i}", :label => label, :shape => "plaintext",
433
- :height => "0.0", :width => "0.0", :margin => "0.0")
440
+ clattice.node("c#{i}", label: label, shape: "plaintext", height: "0.0", width: "0.0", margin: "0.0")
434
441
  end
435
442
  end
436
-
443
+
437
444
  0.upto(@relM.size - 1) do |i|
438
445
  0.upto(@relM.size - 1) do |j|
439
446
  if @relM[i][j] == 1
@@ -441,11 +448,11 @@ class FormalContext
441
448
  end
442
449
  end
443
450
  end
444
-
445
- clattice.subgraph(legend) if opts[:circle] and opts[:legend]
451
+
452
+ clattice.subgraph(legend) if opts[:circle] && opts[:legend]
446
453
  clattice.to_dot
447
454
  end
448
-
455
+
449
456
  ## Generate an actual graphic file (Graphviz dot needs to be installed properly)
450
457
  def generate_img(outfile, image_type, opts)
451
458
  dot = generate_dot(opts)
@@ -453,15 +460,14 @@ class FormalContext
453
460
  if isthere_dot !~ /dot.*version/i
454
461
  showerror("Graphviz's dot program cannot be found.", 1)
455
462
  else
456
- if opts[:straight]
457
- cmd = "dot | neato -n -T#{image_type} -o#{outfile} 2>rubyfca.log"
458
- else
459
- cmd = "dot -T#{image_type} -o#{outfile} 2>rubyfca.log"
460
- end
461
- IO.popen(cmd, 'r+') do |io|
463
+ cmd = if opts[:straight]
464
+ "dot | neato -n -T#{image_type} -o#{outfile} 2>rubyfca.log"
465
+ else
466
+ "dot -T#{image_type} -o#{outfile} 2>rubyfca.log"
467
+ end
468
+ IO.popen(cmd, "r+") do |io|
462
469
  io.puts dot
463
470
  end
464
471
  end
465
- end
472
+ end
466
473
  end
467
-
data/rubyfca.gemspec CHANGED
@@ -1,17 +1,25 @@
1
- # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/rubyfca/version', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("lib/rubyfca/version", __dir__)
3
4
 
4
5
  Gem::Specification.new do |gem|
6
+ gem.name = "rubyfca"
7
+ gem.version = RubyFCA::VERSION
5
8
  gem.authors = ["Yoichiro Hasebe", "Kow Kuroda"]
6
9
  gem.email = ["yohasebe@gmail.com"]
7
- gem.summary = %q{Command line FCA tool written in Ruby}
8
- gem.description = %q{Command line Formal Concept Analysis (FCA) tool written in Ruby}
9
- gem.homepage = "http://github.com/yohasebe/rubyfca"
10
+ gem.summary = "Command line FCA tool written in Ruby"
11
+ gem.description = "Command line Formal Concept Analysis (FCA) tool written in Ruby"
12
+ gem.homepage = "http://github.com/yohasebe/rubyfca"
10
13
 
11
- gem.files = `git ls-files`.split($\)
12
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
- gem.name = "rubyfca"
14
+ gem.required_ruby_version = ">= 2.6.10"
15
+ gem.licenses = ["GPL-3.0"]
16
+ gem.files = `git ls-files`.split("\n")
17
+ gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
15
18
  gem.require_paths = ["lib"]
16
- gem.version = RubyFCA::VERSION
19
+
20
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
+
22
+ gem.add_dependency "optimist"
23
+ gem.add_dependency "roo"
24
+ gem.add_development_dependency "minitest"
17
25
  end
Binary file
Binary file
Binary file
Binary file