rflare 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/Rakefile +8 -0
- data/bin/rflare +59 -0
- data/lib/rflare.rb +181 -0
- data/test/test_rflare.rb +290 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3d3aaf86caee415439c6b079238eb08f64f99361
|
4
|
+
data.tar.gz: 8577586e4feef9089da739591cc7ba6fe0ac13cf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e322a3d7229063a594df73674c2fbb149fcc7a1b8a0a3041cdfcfc2511982ef9c91faf6d08b115949e3c958c069bd6c91cc7513f70a589a98e715c82d20f9197
|
7
|
+
data.tar.gz: eda58c277896406c7d880bc77684b412b19820c27448e8186d75281b280c967aff40faf0db1a74d084b8f4d7c09f3f9c80c566033e393082c41c69be75f4f474
|
data/.gemtest
ADDED
File without changes
|
data/Rakefile
ADDED
data/bin/rflare
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
require 'optparse'
|
5
|
+
require 'tgf'
|
6
|
+
require 'rflare'
|
7
|
+
|
8
|
+
csv_opts = {}
|
9
|
+
nodes, edges = [], []
|
10
|
+
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: rflare [options]"
|
13
|
+
|
14
|
+
opts.on("-F", "--field-separator [FS]") do |fs|
|
15
|
+
csv_opts[:col_sep] = fs
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on("-Q", "--quote-char [QC]") do |qc|
|
19
|
+
csv_opts[:quote_char] = qc
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on("-f", "--file [file]") do |file|
|
23
|
+
nodes, edges = TGF.parse(File(q))
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on("-e", "--evaluate [query]") do |query|
|
27
|
+
nodes, edges = TGF.parse(query, ';')
|
28
|
+
end
|
29
|
+
end.parse!
|
30
|
+
|
31
|
+
def rows_and_cols str
|
32
|
+
str.nil? ? [nil, nil] : str.split(/\s+/, 2)
|
33
|
+
end
|
34
|
+
|
35
|
+
out = CSV.new $stdout, csv_opts
|
36
|
+
ARGV.each do |csv_file|
|
37
|
+
ss = RFlare::Spreadsheet.new(CSV.read(csv_file, csv_opts))
|
38
|
+
my_nodes = nodes.map { |node|
|
39
|
+
if node.label.nil?
|
40
|
+
RFlare::Node.new node.id, nil, nil, nil, ss.row_bounds, ss.col_bounds
|
41
|
+
else
|
42
|
+
m = /^\/((?:.*\\\/)*.*)\/\s*(.+)?/.match node.label
|
43
|
+
rows, columns = rows_and_cols m[2]
|
44
|
+
RFlare::Node.new node.id, m[1], rows, columns, ss.row_bounds, ss.col_bounds
|
45
|
+
end
|
46
|
+
}
|
47
|
+
my_edges = edges.map { |edge|
|
48
|
+
rows, columns = rows_and_cols edge.label
|
49
|
+
RFlare::Edge.new edge.from, edge.to, rows, columns
|
50
|
+
}
|
51
|
+
root = my_nodes[0]
|
52
|
+
|
53
|
+
RFlare::Results.new(my_edges, my_nodes, ss, root).each { |match|
|
54
|
+
n = my_nodes.select {|node| node.id[0] != '_'}
|
55
|
+
out << n.map {|node| match[node.id] || ''}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
out.flush
|
59
|
+
|
data/lib/rflare.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
|
2
|
+
class Array
|
3
|
+
def bounds; 0 ... size; end
|
4
|
+
end
|
5
|
+
|
6
|
+
class Range
|
7
|
+
def cap num
|
8
|
+
num < min ? min : (num > max ? max : num)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module RFlare
|
13
|
+
|
14
|
+
class Square
|
15
|
+
def initialize rows, columns
|
16
|
+
@rows, @columns = rows, columns
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :rows, :columns
|
20
|
+
include Enumerable
|
21
|
+
|
22
|
+
def each
|
23
|
+
@rows.each {|row|
|
24
|
+
@columns.each {|col|
|
25
|
+
yield row, col
|
26
|
+
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def include? row, col
|
31
|
+
@rows.include? row and @columns.include? col
|
32
|
+
end
|
33
|
+
|
34
|
+
def == other
|
35
|
+
@rows == other.rows and @columns == other.columns
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Node
|
40
|
+
def initialize id, match, rows, columns, row_bounds, col_bounds
|
41
|
+
@id = id || '_'
|
42
|
+
@match = Regexp.new(match || '.*')
|
43
|
+
@valid = Square.new(
|
44
|
+
Spec.new(rows || '0:*').range(0, row_bounds),
|
45
|
+
Spec.new(columns || '0:*').range(0, col_bounds))
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :id, :match, :valid
|
49
|
+
|
50
|
+
def matches ss, row, col
|
51
|
+
@valid.include? row, col and (ss[row,col] || '').to_s =~ @match
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Spec
|
56
|
+
def initialize spec
|
57
|
+
@spec = spec.to_s
|
58
|
+
raise "invalid spec '#{@spec}'" if @spec !~ /[+-]?([0-9]+|\*)(:([+-]?([0-9]+|\*))?)?/
|
59
|
+
end
|
60
|
+
|
61
|
+
# + or - means relative to num, otherwise absolute
|
62
|
+
def range num, bounds
|
63
|
+
bits = @spec.split ":", 2
|
64
|
+
s = bits.size == 1 ? @spec : bits[0]
|
65
|
+
e = bits.size == 1 ? @spec : bits[1]
|
66
|
+
range_start(num, bounds, s) .. range_end(num, bounds, e)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def range_start num, bounds, spec
|
72
|
+
if spec == '+*' or spec == '*'
|
73
|
+
num + 1
|
74
|
+
elsif spec == '-*'
|
75
|
+
bounds.min
|
76
|
+
elsif spec[0] == '+' or spec[0] == '-'
|
77
|
+
offset = spec[1, spec.length - 1].to_i
|
78
|
+
spec[0] == '+' ? (num + offset) : (num - offset)
|
79
|
+
elsif spec == ''
|
80
|
+
bounds.min
|
81
|
+
else
|
82
|
+
spec.to_i
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def range_end num, bounds, spec
|
87
|
+
if spec == '+*' or spec == '*' or spec == ""
|
88
|
+
bounds.max
|
89
|
+
elsif spec == '-*'
|
90
|
+
num - 1
|
91
|
+
elsif spec[0] == '+' or spec[0] == '-'
|
92
|
+
offset = spec[1, spec.length - 1].to_i
|
93
|
+
spec[0] == '+' ? (num + offset) : (num - offset)
|
94
|
+
else
|
95
|
+
spec.to_i
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Edge
|
101
|
+
def initialize from, to, vert, horiz
|
102
|
+
@from, @to = from, to
|
103
|
+
@vert = Spec.new(vert || '+0')
|
104
|
+
@horiz = Spec.new(horiz || '+0')
|
105
|
+
end
|
106
|
+
|
107
|
+
attr_reader :from, :to
|
108
|
+
|
109
|
+
def get_square row, col, row_bounds, col_bounds
|
110
|
+
Square.new(
|
111
|
+
@vert.range(row, row_bounds),
|
112
|
+
@horiz.range(col, col_bounds))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class Spreadsheet
|
117
|
+
def initialize arr_of_rows
|
118
|
+
@data = arr_of_rows
|
119
|
+
@row_bounds = @data.bounds
|
120
|
+
@col_bounds = @data.empty? ? (0...0) : @data[0].bounds
|
121
|
+
end
|
122
|
+
|
123
|
+
def [] row, col
|
124
|
+
if @row_bounds.include? row and @col_bounds.include? col
|
125
|
+
@data[row][col]
|
126
|
+
else
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
attr_reader :row_bounds, :col_bounds
|
132
|
+
end
|
133
|
+
|
134
|
+
class Results
|
135
|
+
def initialize edges, nodes, ss, root
|
136
|
+
@ss, @root = ss, root
|
137
|
+
@edges_byfrom = Hash.new {|h,k| h[k] = [] }
|
138
|
+
edges.each {|edge| @edges_byfrom[edge.from] << edge }
|
139
|
+
@nodes_byid = Hash.new
|
140
|
+
nodes.each {|node| @nodes_byid[node.id] = node}
|
141
|
+
end
|
142
|
+
|
143
|
+
include Enumerable
|
144
|
+
|
145
|
+
# start at root, looking for trees
|
146
|
+
def each
|
147
|
+
@root.valid.each {|row, col|
|
148
|
+
matches(row, col, @root).each { |match| yield match }
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def matches row, col, node
|
155
|
+
return [] if !node.matches(@ss, row, col)
|
156
|
+
|
157
|
+
me = {node.id => @ss[row,col]}
|
158
|
+
edges = @edges_byfrom[node.id]
|
159
|
+
return [me] if edges.empty?
|
160
|
+
|
161
|
+
# get array with dims:
|
162
|
+
# (1) each edge from this
|
163
|
+
# -- we flatten the square-for-each-edge dim
|
164
|
+
# (2) each match (a Hash) from edge
|
165
|
+
edge_matches = edges.map do |edge|
|
166
|
+
to = @nodes_byid[edge.to]
|
167
|
+
sq = edge.get_square(row, col, @ss.row_bounds, @ss.col_bounds)
|
168
|
+
paths = sq.flat_map do |sq_row, sq_col|
|
169
|
+
matches sq_row, sq_col, to
|
170
|
+
end
|
171
|
+
paths.select {|a| !a.empty?}
|
172
|
+
end
|
173
|
+
|
174
|
+
[me].product(*edge_matches).map do |assignments_arr|
|
175
|
+
assignments_arr.inject Hash.new, :merge
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
data/test/test_rflare.rb
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'rflare'
|
3
|
+
|
4
|
+
class Hash
|
5
|
+
def <=> other
|
6
|
+
k = keys.sort <=> other.keys.sort
|
7
|
+
return k if k != 0
|
8
|
+
keys.sort.each {|key|
|
9
|
+
k = self[key] <=> other[key]
|
10
|
+
return k if k != 0
|
11
|
+
}
|
12
|
+
0
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class FlashRangeTest < Minitest::Test
|
17
|
+
def test_in
|
18
|
+
assert_equal 4, (2..5).cap(4)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_after
|
22
|
+
assert_equal 5, (2..5).cap(7)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_before
|
26
|
+
assert_equal 2, (2..5).cap(1)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class FlashSquareTest < Minitest::Test
|
31
|
+
def test_include_in
|
32
|
+
assert RFlare::Square.new(2..4, 3..5).include? 3, 4
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_include_out
|
36
|
+
refute RFlare::Square.new(2..4, 3..5).include? 1, 4
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_enumerate
|
40
|
+
assert_equal [[2,3], [2,4], [2,5], [3,3], [3,4], [3,5]], RFlare::Square.new(2..3, 3..5).to_a
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_equals
|
44
|
+
assert_equal RFlare::Square.new(2..3, 4..5), RFlare::Square.new(2..3, 4..5)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class FlashSpecTest < Minitest::Test
|
49
|
+
def setup
|
50
|
+
@bounds = 0..10
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_single_relativeconst
|
54
|
+
assert_equal (4..4), RFlare::Spec.new('+1').range(3, @bounds)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_single_plusstar
|
58
|
+
assert_equal (4..@bounds.max), RFlare::Spec.new('+*').range(3, @bounds)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_single_plusstar2
|
62
|
+
assert_equal (4..@bounds.max), RFlare::Spec.new('*').range(3, @bounds)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_minusone
|
66
|
+
assert_equal (2..2), RFlare::Spec.new('-1').range(3, @bounds)
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_minusstar
|
70
|
+
assert_equal (@bounds.min..2), RFlare::Spec.new('-*').range(3, @bounds)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_abs
|
74
|
+
assert_equal (1..1), RFlare::Spec.new('1').range(3, @bounds)
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_range_relativeconst
|
78
|
+
assert_equal (4..6), RFlare::Spec.new('+1:+3').range(3, @bounds)
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_range_star
|
82
|
+
assert_equal (5..@bounds.max), RFlare::Spec.new('+2:+*').range(3, @bounds)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_invalid
|
86
|
+
assert_raises(RuntimeError) { RFlare::Spec.new 'hat' }
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_range_blank_start
|
90
|
+
assert_equal (@bounds.min..4), RFlare::Spec.new(':4').range(3, @bounds)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_range_blank_end
|
94
|
+
assert_equal (1..@bounds.max), RFlare::Spec.new('1:').range(3, @bounds)
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
class FlashNodeTest < Minitest::Test
|
100
|
+
def setup
|
101
|
+
@ss = RFlare::Spreadsheet.new [[0,1,2],['a','b','c']]
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_match
|
105
|
+
n = RFlare::Node.new nil, nil, 0, nil, @ss.row_bounds, @ss.col_bounds
|
106
|
+
assert_equal RFlare::Square.new(0..0, 0..2), n.valid
|
107
|
+
assert n.matches @ss, 0, 0
|
108
|
+
assert n.matches @ss, 0, 1
|
109
|
+
assert n.matches @ss, 0, 2
|
110
|
+
refute n.matches @ss, 1, 0
|
111
|
+
refute n.matches @ss, 1, 1
|
112
|
+
refute n.matches @ss, 1, 2
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class FlashSpreadsheetTest < Minitest::Test
|
117
|
+
def test_empty
|
118
|
+
ss = RFlare::Spreadsheet.new []
|
119
|
+
assert_equal 0...0, ss.row_bounds
|
120
|
+
assert_equal 0...0, ss.col_bounds
|
121
|
+
assert_nil ss[0,0]
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_full
|
125
|
+
ss = RFlare::Spreadsheet.new [[1,2,3],['a','b','c']]
|
126
|
+
assert_equal 0...2, ss.row_bounds
|
127
|
+
assert_equal 0...3, ss.col_bounds
|
128
|
+
assert_equal 1, ss[0,0]
|
129
|
+
assert_equal 'b', ss[1,1]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class FlashResultsTest < Minitest::Test
|
134
|
+
def setup
|
135
|
+
@ss = RFlare::Spreadsheet.new [
|
136
|
+
[ nil, 'value', 'year', 'value', 'year', 'Comments'],
|
137
|
+
[ 'Albania', 1000, 1950, 930, 1981, 'FRA 1'],
|
138
|
+
[ 'Austria', 3139, 1951, 3177, 1955, 'FRA 3'],
|
139
|
+
[ 'Belgium', 541, 1947, 601, 1950, nil ],
|
140
|
+
['Bulgaria', 2964, 1947, 3259, 1958, 'FRA 1'],
|
141
|
+
[ 'Czech', 2416, 1950, 2503, 1960, 'NC']
|
142
|
+
]
|
143
|
+
end
|
144
|
+
|
145
|
+
def node id, match, rows = nil, columns = nil
|
146
|
+
RFlare::Node.new id, match, rows, columns, @ss.row_bounds, @ss.col_bounds
|
147
|
+
end
|
148
|
+
|
149
|
+
def edge from, to, vert, horiz
|
150
|
+
RFlare::Edge.new from, to, vert, horiz
|
151
|
+
end
|
152
|
+
|
153
|
+
def assert_matches edges, nodes, expected
|
154
|
+
actual = RFlare::Results.new(edges, nodes, @ss, nodes[0]).to_a
|
155
|
+
assert_equal expected.sort, actual.sort
|
156
|
+
end
|
157
|
+
|
158
|
+
# the tests:
|
159
|
+
|
160
|
+
def test_simplecell
|
161
|
+
nodes = [node(3, '^value$')]
|
162
|
+
edges = []
|
163
|
+
expected = [{3 => 'value'}, {3 => 'value'}]
|
164
|
+
assert_matches edges, nodes, expected
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_header
|
168
|
+
nodes = [node(3, '^value$', 0, nil)]
|
169
|
+
edges = []
|
170
|
+
expected = [{3 => 'value'}, {3 => 'value'}]
|
171
|
+
assert_matches edges, nodes, expected
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_oneedge
|
175
|
+
nodes = [node(4,'^[0-9]+$'), node(3, '^value$')]
|
176
|
+
edges = [edge(4, 3, '-*', nil)]
|
177
|
+
expected = [
|
178
|
+
{3 => 'value', 4 => 1000},
|
179
|
+
{3 => 'value', 4 => 3139},
|
180
|
+
{3 => 'value', 4 => 541},
|
181
|
+
{3 => 'value', 4 => 2964},
|
182
|
+
{3 => 'value', 4 => 2416},
|
183
|
+
{3 => 'value', 4 => 930},
|
184
|
+
{3 => 'value', 4 => 3177},
|
185
|
+
{3 => 'value', 4 => 601},
|
186
|
+
{3 => 'value', 4 => 3259},
|
187
|
+
{3 => 'value', 4 => 2503}
|
188
|
+
]
|
189
|
+
assert_matches edges, nodes, expected
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_twoedges
|
193
|
+
nodes = [
|
194
|
+
node('v','^[0-9]+$'),
|
195
|
+
node('vcol', '^value$'),
|
196
|
+
node('y','^19[0-9]{2}$'),
|
197
|
+
]
|
198
|
+
edges = [
|
199
|
+
edge('v', 'vcol', '-*', nil),
|
200
|
+
edge('v', 'y', nil, '+1')
|
201
|
+
]
|
202
|
+
expected = [
|
203
|
+
{'vcol' => 'value', 'v' => 1000, 'y' => 1950},
|
204
|
+
{'vcol' => 'value', 'v' => 3139, 'y' => 1951},
|
205
|
+
{'vcol' => 'value', 'v' => 541, 'y' => 1947},
|
206
|
+
{'vcol' => 'value', 'v' => 2964, 'y' => 1947},
|
207
|
+
{'vcol' => 'value', 'v' => 2416, 'y' => 1950},
|
208
|
+
{'vcol' => 'value', 'v' => 930, 'y' => 1981},
|
209
|
+
{'vcol' => 'value', 'v' => 3177, 'y' => 1955},
|
210
|
+
{'vcol' => 'value', 'v' => 601, 'y' => 1950},
|
211
|
+
{'vcol' => 'value', 'v' => 3259, 'y' => 1958},
|
212
|
+
{'vcol' => 'value', 'v' => 2503, 'y' => 1960}
|
213
|
+
]
|
214
|
+
assert_matches edges, nodes, expected
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_twolevelrecursion
|
218
|
+
nodes = [
|
219
|
+
node('v','^[0-9]+$'),
|
220
|
+
node('vcol', '^value$'),
|
221
|
+
node('y','^19[0-9]{2}$'),
|
222
|
+
node('ycol', '^year$'),
|
223
|
+
]
|
224
|
+
edges = [
|
225
|
+
edge('v', 'vcol', '-*', nil),
|
226
|
+
edge('y', 'ycol', '-*', nil),
|
227
|
+
edge('v', 'y', nil, '+1'),
|
228
|
+
]
|
229
|
+
expected = [
|
230
|
+
{'vcol' => 'value', 'v' => 1000, 'y' => 1950, 'ycol' => 'year'},
|
231
|
+
{'vcol' => 'value', 'v' => 3139, 'y' => 1951, 'ycol' => 'year'},
|
232
|
+
{'vcol' => 'value', 'v' => 541, 'y' => 1947, 'ycol' => 'year'},
|
233
|
+
{'vcol' => 'value', 'v' => 2964, 'y' => 1947, 'ycol' => 'year'},
|
234
|
+
{'vcol' => 'value', 'v' => 2416, 'y' => 1950, 'ycol' => 'year'},
|
235
|
+
{'vcol' => 'value', 'v' => 930, 'y' => 1981, 'ycol' => 'year'},
|
236
|
+
{'vcol' => 'value', 'v' => 3177, 'y' => 1955, 'ycol' => 'year'},
|
237
|
+
{'vcol' => 'value', 'v' => 601, 'y' => 1950, 'ycol' => 'year'},
|
238
|
+
{'vcol' => 'value', 'v' => 3259, 'y' => 1958, 'ycol' => 'year'},
|
239
|
+
{'vcol' => 'value', 'v' => 2503, 'y' => 1960, 'ycol' => 'year'},
|
240
|
+
]
|
241
|
+
assert_matches edges, nodes, expected
|
242
|
+
end
|
243
|
+
|
244
|
+
def test_withrowlimit
|
245
|
+
nodes = [node(3, '^[A-Za-z]+$', '1:*', 0)]
|
246
|
+
edges = []
|
247
|
+
expected = [
|
248
|
+
{3 => 'Albania'},
|
249
|
+
{3 => 'Austria'},
|
250
|
+
{3 => 'Belgium'},
|
251
|
+
{3 => 'Bulgaria'},
|
252
|
+
{3 => 'Czech'},
|
253
|
+
]
|
254
|
+
assert_matches edges, nodes, expected
|
255
|
+
end
|
256
|
+
|
257
|
+
def test_frompaper
|
258
|
+
nodes = [
|
259
|
+
node('c', '^[A-Za-z]+$', '1:*', 0),
|
260
|
+
node('v','^[0-9]+$', '1:*', nil),
|
261
|
+
node('vcol', '^value$', 0, nil),
|
262
|
+
node('y','^19[0-9]{2}$', '1:*', nil),
|
263
|
+
node('ycol', '^year$', 0, nil),
|
264
|
+
node('m','^[A-Za-z 0-9]*$', '1:*', nil),
|
265
|
+
node('mcol', '^Comments$', 0, nil),
|
266
|
+
]
|
267
|
+
edges = [
|
268
|
+
edge('v', 'vcol', '-*', nil),
|
269
|
+
edge('y', 'ycol', '-*', nil),
|
270
|
+
edge('m', 'mcol', '-*', nil),
|
271
|
+
edge('c', 'v', nil, '+*'),
|
272
|
+
edge('v', 'y', nil, '+1'),
|
273
|
+
edge('y', 'm', nil, '+*'),
|
274
|
+
]
|
275
|
+
expected = [
|
276
|
+
{'vcol' => 'value', 'v' => 1000, 'y' => 1950, 'ycol' => 'year', 'c' => 'Albania', 'mcol' => 'Comments', 'm' => 'FRA 1'},
|
277
|
+
{'vcol' => 'value', 'v' => 3139, 'y' => 1951, 'ycol' => 'year', 'c' => 'Austria', 'mcol' => 'Comments', 'm' => 'FRA 3'},
|
278
|
+
{'vcol' => 'value', 'v' => 541, 'y' => 1947, 'ycol' => 'year', 'c' => 'Belgium', 'mcol' => 'Comments', 'm' => nil},
|
279
|
+
{'vcol' => 'value', 'v' => 2964, 'y' => 1947, 'ycol' => 'year', 'c' => 'Bulgaria', 'mcol' => 'Comments', 'm' => 'FRA 1'},
|
280
|
+
{'vcol' => 'value', 'v' => 2416, 'y' => 1950, 'ycol' => 'year', 'c' => 'Czech', 'mcol' => 'Comments', 'm' => 'NC'},
|
281
|
+
{'vcol' => 'value', 'v' => 930, 'y' => 1981, 'ycol' => 'year', 'c' => 'Albania', 'mcol' => 'Comments', 'm' => 'FRA 1'},
|
282
|
+
{'vcol' => 'value', 'v' => 3177, 'y' => 1955, 'ycol' => 'year', 'c' => 'Austria', 'mcol' => 'Comments', 'm' => 'FRA 3'},
|
283
|
+
{'vcol' => 'value', 'v' => 601, 'y' => 1950, 'ycol' => 'year', 'c' => 'Belgium', 'mcol' => 'Comments', 'm' => nil},
|
284
|
+
{'vcol' => 'value', 'v' => 3259, 'y' => 1958, 'ycol' => 'year', 'c' => 'Bulgaria', 'mcol' => 'Comments', 'm' => 'FRA 1'},
|
285
|
+
{'vcol' => 'value', 'v' => 2503, 'y' => 1960, 'ycol' => 'year', 'c' => 'Czech', 'mcol' => 'Comments', 'm' => 'NC'},
|
286
|
+
]
|
287
|
+
assert_matches edges, nodes, expected
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rflare
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Remis
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: tgf
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10'
|
41
|
+
description: A command-line tool to extract relational data from semi-structured CSV
|
42
|
+
files
|
43
|
+
email: remis.thoughts@gmail.com
|
44
|
+
executables:
|
45
|
+
- rflare
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".gemtest"
|
50
|
+
- Rakefile
|
51
|
+
- bin/rflare
|
52
|
+
- lib/rflare.rb
|
53
|
+
- test/test_rflare.rb
|
54
|
+
homepage: https://research.microsoft.com/pubs/214302/flashrelate-tech-report-April2014.pdf
|
55
|
+
licenses:
|
56
|
+
- Apache-2.0
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 2.2.2
|
75
|
+
signing_key:
|
76
|
+
specification_version: 4
|
77
|
+
summary: Ruby version of Flare
|
78
|
+
test_files:
|
79
|
+
- test/test_rflare.rb
|