optimus-ep 0.6.0 → 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +1 -0
- data/bin/eprime2tabfile +6 -6
- data/bin/extract_timings +2 -4
- data/lib/eprime_data.rb +41 -24
- data/lib/eprime_reader.rb +8 -18
- data/lib/eprimetab_parser.rb +10 -0
- data/lib/excel_parser.rb +5 -0
- data/lib/log_file_parser.rb +5 -0
- data/lib/raw_tab_parser.rb +27 -0
- data/lib/runners/generic_runner.rb +6 -15
- data/lib/tabfile_parser.rb +1 -3
- data/lib/transformers/column_calculator.rb +53 -35
- data/lib/transformers/multipasser.rb +25 -22
- data/lib/transformers/row_filter.rb +21 -7
- data/lib/version.rb +1 -1
- data/spec/eprime_data_spec.rb +4 -0
- data/spec/eprime_reader_spec.rb +15 -3
- data/spec/runners/generic_runner_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -0
- data/spec/transformers/column_calculator_spec.rb +38 -7
- data/spec/transformers/multipasser_spec.rb +1 -0
- data/spec/transformers/row_filter_spec.rb +1 -0
- metadata +15 -3
data/Manifest.txt
CHANGED
data/bin/eprime2tabfile
CHANGED
@@ -84,7 +84,7 @@ module Eprime
|
|
84
84
|
opts.on('-o', '--outfile=OUTFILE', String,
|
85
85
|
'The name of the file to create. If this',
|
86
86
|
'isn\'t specified, print to the standard',
|
87
|
-
"output"
|
87
|
+
"output."
|
88
88
|
) { |val|
|
89
89
|
option_hash[:outfile] = val
|
90
90
|
}
|
@@ -92,14 +92,14 @@ module Eprime
|
|
92
92
|
|
93
93
|
opts.on('-c', '--columns=COLUMN_FILE', String,
|
94
94
|
'A tab-separated file containing the columns',
|
95
|
-
"in the order you want your output"
|
95
|
+
"in the order you want your output."
|
96
96
|
) { |val|
|
97
97
|
option_hash[:column_file] = val
|
98
98
|
}
|
99
99
|
opts.separator ""
|
100
100
|
|
101
101
|
opts.on('--filter-columns', TrueClass,
|
102
|
-
'Write out only the columns in COLUMN_FILE',
|
102
|
+
'Write out only the columns in COLUMN_FILE.',
|
103
103
|
'Requires the use of --columns'
|
104
104
|
) {
|
105
105
|
option_hash[:filter_columns] = true
|
@@ -109,21 +109,21 @@ module Eprime
|
|
109
109
|
|
110
110
|
opts.on('-a', '--add-filename-line', TrueClass,
|
111
111
|
'Print the filename as the first line of',
|
112
|
-
"your output, just like E-DataAid"
|
112
|
+
"your output, just like E-DataAid."
|
113
113
|
) {
|
114
114
|
option_hash[:add_filename_line] = true
|
115
115
|
}
|
116
116
|
opts.separator ""
|
117
117
|
|
118
118
|
opts.on('-f', '--force', TrueClass,
|
119
|
-
"Continue processing even there are errors"
|
119
|
+
"Continue processing even there are errors."
|
120
120
|
) {
|
121
121
|
option_hash[:force] = true
|
122
122
|
}
|
123
123
|
opts.separator ""
|
124
124
|
|
125
125
|
opts.on('-h', '--help', TrueClass,
|
126
|
-
'Print this message'
|
126
|
+
'Print this message.'
|
127
127
|
) {
|
128
128
|
option_hash[:help] = true
|
129
129
|
}
|
data/bin/extract_timings
CHANGED
@@ -12,10 +12,8 @@ require 'optparse'
|
|
12
12
|
gem 'optimus-ep'
|
13
13
|
require 'runners/generic_runner'
|
14
14
|
|
15
|
-
script_name = File.basename(__FILE__)
|
16
|
-
|
17
15
|
begin
|
18
|
-
txr = Eprime::Runners::GenericRunner.new(Eprime::Transformers::TimingExtractor,
|
16
|
+
txr = Eprime::Runners::GenericRunner.new(Eprime::Transformers::TimingExtractor, ARGV)
|
19
17
|
txr.process!
|
20
18
|
rescue ArgumentError => e
|
21
19
|
STDERR.puts e.message
|
@@ -23,4 +21,4 @@ rescue ArgumentError => e
|
|
23
21
|
rescue Exception => e
|
24
22
|
STDERR.puts e.message
|
25
23
|
exit 2
|
26
|
-
end
|
24
|
+
end
|
data/lib/eprime_data.rb
CHANGED
@@ -52,9 +52,7 @@ module Eprime
|
|
52
52
|
@column_hash = {}
|
53
53
|
@columns_set_in_initialize = false
|
54
54
|
if (columns && columns.length > 0)
|
55
|
-
columns
|
56
|
-
idx = self.find_or_add_column_index(col)
|
57
|
-
end
|
55
|
+
add_columns!(columns)
|
58
56
|
@columns_set_in_initialize = true
|
59
57
|
end
|
60
58
|
end
|
@@ -62,19 +60,31 @@ module Eprime
|
|
62
60
|
# Returns a new Eprime::Data object containing the data from this
|
63
61
|
# and all other data sets
|
64
62
|
def merge(*datasets)
|
65
|
-
|
63
|
+
cols = [self, *datasets].map { |d| d.columns }.flatten.uniq
|
64
|
+
d = Eprime::Data.new(cols)
|
66
65
|
return d.merge!(self, *datasets)
|
67
66
|
end
|
68
67
|
|
69
68
|
# Combine more Eprime::Data objects into this one, in-place
|
70
69
|
def merge!(*datasets)
|
71
70
|
datasets.each do |source|
|
72
|
-
source.
|
73
|
-
|
74
|
-
|
75
|
-
|
71
|
+
add_columns!(source.columns)
|
72
|
+
if source.columns == self.columns
|
73
|
+
# The fast option
|
74
|
+
source.each do |row|
|
75
|
+
r = self.add_row
|
76
|
+
r.sort_value = row.sort_value
|
77
|
+
r.values = row.values
|
78
|
+
end
|
79
|
+
else
|
80
|
+
# The general case option
|
81
|
+
source.each do |row|
|
82
|
+
r = self.add_row
|
83
|
+
row.columns.each do |col|
|
84
|
+
r[col] = row[col]
|
85
|
+
end
|
86
|
+
r.sort_value = row.sort_value
|
76
87
|
end
|
77
|
-
r.sort_value = row.sort_value
|
78
88
|
end
|
79
89
|
end
|
80
90
|
return self
|
@@ -99,18 +109,18 @@ module Eprime
|
|
99
109
|
@rows.send method, *args, &block
|
100
110
|
end
|
101
111
|
|
102
|
-
def add_row
|
112
|
+
def add_row()
|
103
113
|
row = Row.new(self)
|
104
114
|
@rows << row
|
105
115
|
return row
|
106
116
|
end
|
107
117
|
|
118
|
+
#def add_row_values(values, sort_value = 1)
|
119
|
+
# r = Row.new(self, values, sort_value)
|
120
|
+
#end
|
121
|
+
|
108
122
|
def find_column_index(col_id)
|
109
|
-
|
110
|
-
return (col_id < @columns.size) ? col_id : nil
|
111
|
-
end
|
112
|
-
# Short-circuit this
|
113
|
-
@column_hash[col_id] if @column_hash[col_id]
|
123
|
+
@column_hash[col_id]
|
114
124
|
end
|
115
125
|
|
116
126
|
def find_or_add_column_index(col_id)
|
@@ -120,23 +130,31 @@ module Eprime
|
|
120
130
|
# indexes.
|
121
131
|
return index_id if index_id or col_id.is_a?(Fixnum)
|
122
132
|
# In this case, we're adding a column...
|
133
|
+
index = @columns.size
|
123
134
|
@columns << col_id
|
124
|
-
index = @columns.length - 1
|
125
135
|
@column_hash[col_id] = index
|
136
|
+
@column_hash[index] = index
|
126
137
|
if @columns_set_in_initialize and not @options[:ignore_warnings]
|
127
138
|
raise ColumnAddedWarning.new("Error: Added column #{col_id} after specifying columns at init", index)
|
128
139
|
end
|
129
140
|
return index
|
130
141
|
end
|
131
142
|
|
143
|
+
private
|
144
|
+
def add_columns!(col_arr)
|
145
|
+
col_arr.each do |c|
|
146
|
+
find_or_add_column_index(c)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
132
150
|
class Row
|
133
151
|
attr_accessor :sort_value
|
134
152
|
|
135
|
-
def initialize(parent)
|
136
|
-
@data = []
|
153
|
+
def initialize(parent, data = [], sort_value = 1)
|
137
154
|
@parent = parent
|
155
|
+
@data = data
|
138
156
|
# Ensure it's comparable
|
139
|
-
@sort_value =
|
157
|
+
@sort_value = sort_value
|
140
158
|
end
|
141
159
|
|
142
160
|
def [](index)
|
@@ -164,13 +182,12 @@ module Eprime
|
|
164
182
|
end
|
165
183
|
|
166
184
|
def values
|
167
|
-
|
168
|
-
@parent.columns.each_index do |i|
|
169
|
-
vals[i] = @data[i]
|
170
|
-
end
|
171
|
-
return vals
|
185
|
+
return @data
|
172
186
|
end
|
173
187
|
|
188
|
+
def values=(ar)
|
189
|
+
@data = ar
|
190
|
+
end
|
174
191
|
end
|
175
192
|
end
|
176
193
|
|
data/lib/eprime_reader.rb
CHANGED
@@ -8,6 +8,7 @@
|
|
8
8
|
require 'log_file_parser'
|
9
9
|
require 'excel_parser'
|
10
10
|
require 'eprimetab_parser'
|
11
|
+
require 'raw_tab_parser'
|
11
12
|
|
12
13
|
module Eprime
|
13
14
|
|
@@ -19,7 +20,8 @@ module Eprime
|
|
19
20
|
attr_reader :type, :parser, :input
|
20
21
|
attr_accessor :options
|
21
22
|
|
22
|
-
|
23
|
+
PARSERS = [LogfileParser, ExcelParser, EprimetabParser, RawTabParser]
|
24
|
+
|
23
25
|
def initialize(input = nil, options = {})
|
24
26
|
@options = options || {}
|
25
27
|
set_input(input) unless input.nil?
|
@@ -36,7 +38,6 @@ module Eprime
|
|
36
38
|
|
37
39
|
def options=(options)
|
38
40
|
@options = options || {}
|
39
|
-
set_parser!
|
40
41
|
end
|
41
42
|
|
42
43
|
private
|
@@ -72,17 +73,12 @@ module Eprime
|
|
72
73
|
if @type.nil?
|
73
74
|
raise UnknownTypeError.new("Can't determine the type of #{file.path}")
|
74
75
|
end
|
75
|
-
set_parser!
|
76
|
-
end
|
77
|
-
|
78
|
-
def set_parser!
|
79
76
|
@eprime_data = nil
|
80
|
-
|
81
|
-
@parser = TYPES[@type].new(@file, @options)
|
77
|
+
@parser = @type.new(@file, @options)
|
82
78
|
end
|
83
79
|
|
84
80
|
# Determines the type of an eprime file, based on its first two lines.
|
85
|
-
# Returns one of
|
81
|
+
# Returns one of the elements of PARSERS or nil
|
86
82
|
def determine_file_type(first_lines)
|
87
83
|
# Log files start with *** Header Start ***
|
88
84
|
#
|
@@ -91,15 +87,9 @@ module Eprime
|
|
91
87
|
#
|
92
88
|
# eprime CSV files will have at least three tab-delimited elements on the first line
|
93
89
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
return :excel
|
98
|
-
elsif (first_lines[0].split("\t").size >= 3 and first_lines[1].split("\t").size >= 3)
|
99
|
-
return :eprime
|
100
|
-
end
|
101
|
-
# Don't know? Return nil.
|
102
|
-
return nil
|
90
|
+
return PARSERS.detect { |parser_class|
|
91
|
+
parser_class.can_parse?(first_lines)
|
92
|
+
}
|
103
93
|
end
|
104
94
|
end
|
105
95
|
end
|
data/lib/eprimetab_parser.rb
CHANGED
@@ -16,6 +16,16 @@ module Eprime
|
|
16
16
|
options = options.merge(:skip_lines => 3)
|
17
17
|
super(file, options)
|
18
18
|
end
|
19
|
+
|
20
|
+
def self.can_parse?(lines)
|
21
|
+
divided = lines.map { |l| l.strip.split("\t") }
|
22
|
+
return (
|
23
|
+
divided[0].size >= 3 and
|
24
|
+
divided[0].size == divided[1].size and
|
25
|
+
divided[0][0] == 'STRING' and
|
26
|
+
divided[1][0] == 'EXPNAME'
|
27
|
+
)
|
28
|
+
end
|
19
29
|
end
|
20
30
|
end
|
21
31
|
end
|
data/lib/excel_parser.rb
CHANGED
data/lib/log_file_parser.rb
CHANGED
@@ -11,6 +11,7 @@ module Eprime
|
|
11
11
|
# Reads and parses E-Prime log files (the ones that start with
|
12
12
|
# *** Header Start ***) and transforms them into an Eprime::Data structure
|
13
13
|
|
14
|
+
|
14
15
|
class LogfileParser
|
15
16
|
# Handles parsing eprime log files, which are essentially a blow-by-blow
|
16
17
|
# log of everything that happened during an eprime run.
|
@@ -93,6 +94,10 @@ module Eprime
|
|
93
94
|
@skip_columns[col_name] = true
|
94
95
|
end
|
95
96
|
|
97
|
+
def self.can_parse?(lines)
|
98
|
+
lines[0].include?('*** Header Start ***')
|
99
|
+
end
|
100
|
+
|
96
101
|
private
|
97
102
|
# iterate over each line, strip it, look for *** LogFrame Start *** and
|
98
103
|
# *** LogFrame End *** -- the content between those goes into a frame array.
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Part of the Optimus package for managing E-Prime data
|
2
|
+
#
|
3
|
+
# Copyright (C) 2008 Board of Regents of the University of Wisconsin System
|
4
|
+
#
|
5
|
+
# Written by Nathan Vack <njvack@wisc.edu>, at the Waisman Laborotory for Brain
|
6
|
+
# Imaging and Behavior, University of Wisconsin - Madison
|
7
|
+
|
8
|
+
# This almost completely delegates to TabfileParser
|
9
|
+
# It differs from EprimetabParser only in that it doesn't skip any lines.
|
10
|
+
|
11
|
+
require 'tabfile_parser'
|
12
|
+
|
13
|
+
module Eprime
|
14
|
+
class Reader
|
15
|
+
class RawTabParser < TabfileParser
|
16
|
+
def initialize(file, options = {})
|
17
|
+
options = options.merge(:skip_lines => 0)
|
18
|
+
super(file, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.can_parse?(lines)
|
22
|
+
ary = lines.map { |l| l.strip.split("\t") }
|
23
|
+
ary[0].size > 1 and ary.all? {|e| e.size == ary[0].size}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -5,19 +5,9 @@
|
|
5
5
|
# Written by Nathan Vack <njvack@wisc.edu>, at the Waisman Laborotory for Brain
|
6
6
|
# Imaging and Behavior, University of Wisconsin - Madison
|
7
7
|
|
8
|
-
# A runner to take eprime data files, chew them through a pesudo-templater,
|
9
|
-
# and produce delicious files for importing into other packages. They'll look
|
10
|
-
# like:
|
11
|
-
#
|
12
|
-
# presented onset offset
|
13
|
-
# stim1 5992 6493
|
14
|
-
# stim2 7294 7981
|
15
8
|
#
|
16
9
|
# This class should handle argument processing, file I/O, and such.
|
17
10
|
|
18
|
-
# TODO: Think up a clever way to make this handle arbitrary transformers
|
19
|
-
# Probably this is possible by passing the class of the transformer to
|
20
|
-
# this runner, and instance_eval()'ing the template file in its presence.
|
21
11
|
|
22
12
|
require 'eprime'
|
23
13
|
require 'eprime_reader'
|
@@ -32,9 +22,11 @@ module Eprime
|
|
32
22
|
include ::Eprime::Transformers
|
33
23
|
|
34
24
|
attr_accessor :out, :err
|
35
|
-
def initialize(extractor_class,
|
25
|
+
def initialize(extractor_class, *args)
|
36
26
|
@extractor_class = extractor_class
|
37
|
-
|
27
|
+
# caller() returns an array of 'filename:line' -- the last element
|
28
|
+
# should contain the name of the script that started this process
|
29
|
+
@script_name = File.basename(caller.last.split(':').first)
|
38
30
|
@out = STDOUT
|
39
31
|
@err = STDERR
|
40
32
|
@args = args
|
@@ -51,11 +43,10 @@ module Eprime
|
|
51
43
|
end
|
52
44
|
|
53
45
|
def read_data
|
54
|
-
data = Eprime::Data.new
|
46
|
+
data = Eprime::Data.new()
|
55
47
|
@options.input_files.each do |infile|
|
56
48
|
File.open(infile) do |f|
|
57
|
-
|
58
|
-
data.merge!(new_data)
|
49
|
+
data.merge!(Eprime::Reader.new(f).eprime_data)
|
59
50
|
end
|
60
51
|
end
|
61
52
|
@data = data
|
data/lib/tabfile_parser.rb
CHANGED
@@ -44,9 +44,7 @@ module Eprime
|
|
44
44
|
if col_data.size != expected_size
|
45
45
|
raise DamagedFileError.new("In #{@file.path}, line #{current_line} should have #{expected_size} columns but had #{col_data.size}.")
|
46
46
|
end
|
47
|
-
|
48
|
-
row[i] = col_data[i]
|
49
|
-
end
|
47
|
+
row.values = col_data
|
50
48
|
end
|
51
49
|
return data
|
52
50
|
end
|
@@ -32,6 +32,7 @@ module Eprime
|
|
32
32
|
@columns = []
|
33
33
|
@columns_intern = []
|
34
34
|
@column_indexes = {}
|
35
|
+
@computed = nil
|
35
36
|
@rows = []
|
36
37
|
COLUMN_TYPES.each do |type|
|
37
38
|
instance_variable_set("@#{type}", [])
|
@@ -42,12 +43,11 @@ module Eprime
|
|
42
43
|
|
43
44
|
# Makes this into a static Eprime::Data object
|
44
45
|
def to_eprime_data
|
45
|
-
|
46
|
+
computed
|
46
47
|
end
|
47
48
|
|
48
49
|
def data=(data)
|
49
50
|
@data = data
|
50
|
-
|
51
51
|
@data_cols = []
|
52
52
|
@data.columns.each do |col_name|
|
53
53
|
@data_cols << DataColumn.new(col_name, @data)
|
@@ -56,26 +56,28 @@ module Eprime
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def [](index)
|
59
|
-
|
60
|
-
return @rows[index]
|
59
|
+
computed[index]
|
61
60
|
end
|
62
61
|
|
63
62
|
def column_index(col_id)
|
64
|
-
if col_id.is_a? Fixnum
|
65
|
-
return (col_id >= 0 and col_id < @columns.size) ? col_id : nil
|
66
|
-
end
|
67
63
|
return @column_indexes[col_id]
|
68
64
|
end
|
65
|
+
|
66
|
+
alias :find_column_index :column_index
|
69
67
|
|
70
68
|
def column(col_id)
|
71
69
|
index = column_index(col_id)
|
72
70
|
raise IndexError.new("#{col_id} does not exist") if index.nil?
|
73
71
|
return @columns_intern[index]
|
74
72
|
end
|
75
|
-
|
73
|
+
|
76
74
|
def size
|
77
75
|
@data.size
|
78
76
|
end
|
77
|
+
|
78
|
+
def columns
|
79
|
+
@columns.dup
|
80
|
+
end
|
79
81
|
|
80
82
|
def computed_column(name, expression)
|
81
83
|
@computed_cols << ComputedColumn.new(name, Expression.new(expression))
|
@@ -95,18 +97,15 @@ module Eprime
|
|
95
97
|
def sort_expression=(expr)
|
96
98
|
# The name 'sorter' is utterly arbitrary and never used
|
97
99
|
@sorter = ComputedColumn.new('sorter', Expression.new(expr))
|
98
|
-
@computed =
|
100
|
+
@computed = nil
|
99
101
|
end
|
100
102
|
|
101
103
|
def sort_expression
|
102
104
|
@sorter.to_s
|
103
105
|
end
|
104
106
|
|
105
|
-
def each
|
106
|
-
|
107
|
-
yield self[row_index]
|
108
|
-
end
|
109
|
-
@rows
|
107
|
+
def each(&block)
|
108
|
+
computed.each(&block)
|
110
109
|
end
|
111
110
|
|
112
111
|
def self.compute(numeric_expression)
|
@@ -114,18 +113,23 @@ module Eprime
|
|
114
113
|
end
|
115
114
|
|
116
115
|
private
|
117
|
-
|
116
|
+
|
117
|
+
def computed
|
118
|
+
@computed || compute_data!
|
119
|
+
end
|
120
|
+
|
118
121
|
def add_column(column)
|
119
122
|
# Raise an error if the column already exists
|
120
123
|
if @column_indexes[column.name]
|
121
124
|
raise ComputationError.new("#{column.name} already exists!")
|
122
125
|
end
|
123
126
|
# Save the index
|
127
|
+
@column_indexes[@columns_intern.size] = @columns_intern.size
|
124
128
|
@column_indexes[column.name] = @columns_intern.size
|
125
129
|
@columns_intern << column
|
126
130
|
@columns << column.name
|
127
131
|
end
|
128
|
-
|
132
|
+
|
129
133
|
def set_columns!
|
130
134
|
@columns = []
|
131
135
|
@columns_intern = []
|
@@ -136,9 +140,9 @@ module Eprime
|
|
136
140
|
add_column(col)
|
137
141
|
end
|
138
142
|
end
|
139
|
-
@computed =
|
143
|
+
@computed = nil
|
140
144
|
end
|
141
|
-
|
145
|
+
|
142
146
|
# Creates the infix calculator -- called at class instantiation time
|
143
147
|
def self.make_calculator
|
144
148
|
@@calculator = ::Eprime::Calculator.new
|
@@ -150,9 +154,9 @@ module Eprime
|
|
150
154
|
# because copydown and counter columns depend on the values of previous
|
151
155
|
# rows.
|
152
156
|
def compute_data!
|
153
|
-
@
|
154
|
-
@data.each_index do |
|
155
|
-
row = Row.new(self, @data[
|
157
|
+
@computed = Eprime::Data.new(columns)
|
158
|
+
@data.each_index do |i|
|
159
|
+
row = Row.new(self, @data[i])
|
156
160
|
# Loop over each column type -- it's still (slighyly) important that
|
157
161
|
# we go over each column type specifically. When counter columns
|
158
162
|
# work better, we can rearchitect this a bit.
|
@@ -173,12 +177,13 @@ module Eprime
|
|
173
177
|
rescue ArgumentError
|
174
178
|
# If this fails, it's OK -- we just won't convert.
|
175
179
|
end
|
176
|
-
|
177
|
-
|
180
|
+
new_row = @computed.add_row
|
181
|
+
new_row.sort_value = sv
|
182
|
+
new_row.values = row.values
|
178
183
|
end
|
179
|
-
@computed
|
184
|
+
@computed
|
180
185
|
end
|
181
|
-
|
186
|
+
|
182
187
|
class Column
|
183
188
|
attr_accessor :name
|
184
189
|
|
@@ -233,6 +238,10 @@ module Eprime
|
|
233
238
|
if path.include?(@name)
|
234
239
|
raise ComputationError.new("#{compute_str} contains a loop with #{@name} -- can't compute")
|
235
240
|
end
|
241
|
+
|
242
|
+
# Allow defining the column computation as a lambda
|
243
|
+
return @expression.expr.call(row) if @expression.expr.is_a? Proc
|
244
|
+
|
236
245
|
|
237
246
|
column_names = @expression.columns
|
238
247
|
column_names.each do |col_name|
|
@@ -285,17 +294,23 @@ module Eprime
|
|
285
294
|
end
|
286
295
|
|
287
296
|
class Row
|
288
|
-
attr_reader :
|
297
|
+
attr_reader :values
|
289
298
|
attr_accessor :sort_value
|
290
299
|
|
291
300
|
def initialize(parent, rowdata)
|
292
301
|
@parent = parent
|
293
302
|
@rowdata = rowdata
|
294
|
-
@
|
295
|
-
# Add all the data columns to
|
303
|
+
@values = []
|
304
|
+
# Add all the data columns to @values
|
296
305
|
rowdata.columns.each do |dcol_name|
|
297
306
|
index = @parent.column_index(dcol_name)
|
298
|
-
|
307
|
+
rd = rowdata[dcol_name]
|
308
|
+
begin
|
309
|
+
ci = values[index]
|
310
|
+
rescue Exception => e
|
311
|
+
raise e
|
312
|
+
end
|
313
|
+
@values[index] = rowdata[dcol_name]
|
299
314
|
end
|
300
315
|
@sort_value = 1
|
301
316
|
end
|
@@ -304,7 +319,7 @@ module Eprime
|
|
304
319
|
if @parent.column_index(col_id).nil?
|
305
320
|
raise IndexError.new("#{col_id} does not exist")
|
306
321
|
end
|
307
|
-
return @
|
322
|
+
return @values[@parent.column_index(col_id)]
|
308
323
|
end
|
309
324
|
|
310
325
|
def find_column(column_name)
|
@@ -322,28 +337,31 @@ module Eprime
|
|
322
337
|
|
323
338
|
index = @parent.column_index(col_name)
|
324
339
|
col = @parent.column(col_name)
|
325
|
-
|
326
|
-
|
340
|
+
val = col.compute(self)
|
341
|
+
@values[index] = val
|
342
|
+
return val
|
327
343
|
end
|
328
344
|
|
329
345
|
def <=>(other_row)
|
330
346
|
@sort_value <=> other_row.sort_value
|
331
347
|
end
|
332
|
-
|
333
348
|
end
|
334
349
|
|
335
350
|
|
336
351
|
class Expression
|
337
352
|
attr_reader :columns
|
353
|
+
attr_reader :expr
|
338
354
|
|
339
355
|
COLUMN_FINDER = /\{([^}]*)\}/ # Finds strings like {foo} and {bar}
|
340
356
|
def initialize(expr_string)
|
341
357
|
@expr = expr_string
|
342
|
-
|
358
|
+
unless expr_string.is_a? Proc
|
359
|
+
@columns = find_columns(expr_string).freeze
|
360
|
+
end
|
343
361
|
end
|
344
362
|
|
345
363
|
def to_s
|
346
|
-
@expr.dup
|
364
|
+
@expr.to_s.dup
|
347
365
|
end
|
348
366
|
|
349
367
|
private
|
@@ -43,28 +43,20 @@ module Eprime
|
|
43
43
|
def initialize(data = nil)
|
44
44
|
@data = data
|
45
45
|
@passes = []
|
46
|
-
|
46
|
+
reset!
|
47
47
|
end
|
48
48
|
|
49
49
|
def columns
|
50
|
-
|
51
|
-
@all_data.columns.dup
|
50
|
+
computed.columns
|
52
51
|
end
|
53
52
|
|
54
53
|
def data=(data)
|
55
54
|
@data = data
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
def each
|
60
|
-
compute! unless @computed
|
61
|
-
@all_data.each do |row|
|
62
|
-
yield row
|
63
|
-
end
|
55
|
+
reset!
|
64
56
|
end
|
65
57
|
|
66
58
|
def add_pass(*args)
|
67
|
-
@computed =
|
59
|
+
@computed = nil
|
68
60
|
if args[0].instance_of? Pass
|
69
61
|
p = args[0]
|
70
62
|
else
|
@@ -73,33 +65,44 @@ module Eprime
|
|
73
65
|
@passes << p and return p
|
74
66
|
end
|
75
67
|
|
68
|
+
def each(&block)
|
69
|
+
computed.each(&block)
|
70
|
+
end
|
71
|
+
|
76
72
|
def [](index)
|
77
|
-
|
78
|
-
return @all_data[index]
|
73
|
+
computed[index]
|
79
74
|
end
|
80
75
|
|
81
76
|
private
|
82
|
-
def
|
83
|
-
@
|
77
|
+
def computed
|
78
|
+
return @computed if @computed
|
79
|
+
if @passes.empty?
|
80
|
+
@computed = @data
|
81
|
+
return @computed
|
82
|
+
end
|
83
|
+
@computed = Eprime::Data.new
|
84
84
|
# Just add a simple pass if we don't have any...
|
85
|
-
add_pass if @passes.empty?
|
86
85
|
@passes.each do |pass|
|
87
86
|
# We want to duplicate the data, add a sort expression to it, add
|
88
87
|
# computed columns, filter it, and then merge it into the complete
|
89
88
|
# dataset.
|
90
|
-
cur_data = @data.to_eprime_data
|
89
|
+
#cur_data = @data.to_eprime_data
|
91
90
|
comp_data = ColumnCalculator.new
|
92
|
-
comp_data.data =
|
91
|
+
comp_data.data = @data
|
93
92
|
comp_data.sort_expression = pass.sort_expression
|
94
93
|
pass.computed_columns.each do |col|
|
95
94
|
name, expr = *col
|
96
95
|
comp_data.computed_column(name, expr)
|
97
96
|
end
|
98
97
|
filtered = RowFilter.new(comp_data, pass.row_filter)
|
99
|
-
@
|
98
|
+
@computed.merge!(filtered)
|
100
99
|
end
|
101
|
-
@
|
102
|
-
@computed
|
100
|
+
@computed.sort!
|
101
|
+
return @computed
|
102
|
+
end
|
103
|
+
|
104
|
+
def reset!
|
105
|
+
@computed = nil
|
103
106
|
end
|
104
107
|
end
|
105
108
|
end
|
@@ -17,18 +17,32 @@ module Eprime
|
|
17
17
|
def initialize(data, filter)
|
18
18
|
@data = data
|
19
19
|
@filter = filter
|
20
|
+
@computed = nil
|
20
21
|
end
|
21
22
|
|
22
23
|
def to_eprime_data
|
23
|
-
|
24
|
+
computed
|
24
25
|
end
|
25
|
-
|
26
|
-
def
|
27
|
-
|
28
|
-
yield row if match?(row)
|
29
|
-
end
|
26
|
+
|
27
|
+
def method_missing(method, *args, &block)
|
28
|
+
computed.send method, *args, &block
|
30
29
|
end
|
31
|
-
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def computed
|
34
|
+
return @computed if @computed
|
35
|
+
@computed = Eprime::Data.new(@data.columns)
|
36
|
+
@data.find_all{ |row|
|
37
|
+
match?(row)
|
38
|
+
}.each { |row|
|
39
|
+
r = @computed.add_row
|
40
|
+
r.values = row.values
|
41
|
+
r.sort_value = row.sort_value
|
42
|
+
}
|
43
|
+
return @computed
|
44
|
+
end
|
45
|
+
|
32
46
|
def match?(row)
|
33
47
|
if @filter.is_a? Proc
|
34
48
|
return @filter.call(row)
|
data/lib/version.rb
CHANGED
data/spec/eprime_data_spec.rb
CHANGED
@@ -45,6 +45,10 @@ shared_examples_for "Eprime::Data with one row" do
|
|
45
45
|
@row[index] = "bitten"
|
46
46
|
@row[index].should == "bitten"
|
47
47
|
end
|
48
|
+
|
49
|
+
it "should not lose columns when duplicating" do
|
50
|
+
@data.columns.should == @data.dup.columns
|
51
|
+
end
|
48
52
|
|
49
53
|
it "should raise when setting out-of-bound column value" do
|
50
54
|
lambda {
|
data/spec/eprime_reader_spec.rb
CHANGED
@@ -40,7 +40,7 @@ describe Eprime::Reader do
|
|
40
40
|
|
41
41
|
it "should detect log files" do
|
42
42
|
@reader.input = (@file)
|
43
|
-
@reader.type.should ==
|
43
|
+
@reader.type.should == Eprime::Reader::LogfileParser
|
44
44
|
end
|
45
45
|
|
46
46
|
it "should return the Eprime::Reader::LogfileParser" do
|
@@ -72,7 +72,7 @@ describe Eprime::Reader do
|
|
72
72
|
|
73
73
|
it "should detect excel csv files" do
|
74
74
|
@reader.input = @file
|
75
|
-
@reader.type.should ==
|
75
|
+
@reader.type.should == Eprime::Reader::ExcelParser
|
76
76
|
end
|
77
77
|
|
78
78
|
it "should resutn the Eprime::Reader::ExcelParser" do
|
@@ -96,7 +96,7 @@ describe Eprime::Reader do
|
|
96
96
|
|
97
97
|
it "should detect eprime csv files" do
|
98
98
|
@reader.input = @file
|
99
|
-
@reader.type.should ==
|
99
|
+
@reader.type.should == Eprime::Reader::EprimetabParser
|
100
100
|
end
|
101
101
|
|
102
102
|
it "should return the Eprime::Reader::EprimetabParser" do
|
@@ -111,5 +111,17 @@ describe Eprime::Reader do
|
|
111
111
|
data.columns.sort.should == SORTED_COLUMNS
|
112
112
|
end
|
113
113
|
end
|
114
|
+
|
115
|
+
describe "with raw tsv files" do
|
116
|
+
before :each do
|
117
|
+
@file = File.open(RAW_TSV_FILE)
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
it "should detect tsv files" do
|
122
|
+
@reader.input = @file
|
123
|
+
@reader.type.should == Eprime::Reader::RawTabParser
|
124
|
+
end
|
125
|
+
end
|
114
126
|
|
115
127
|
end
|
@@ -19,7 +19,7 @@ include EprimeTestHelper
|
|
19
19
|
|
20
20
|
describe Eprime::Runners::GenericRunner do
|
21
21
|
before :each do
|
22
|
-
@txr = Eprime::Runners::GenericRunner.new
|
22
|
+
@txr = Eprime::Runners::GenericRunner.new(Eprime::Transformers::BasicTransformer)
|
23
23
|
end
|
24
24
|
|
25
25
|
it "should start with stdout in @out" do
|
data/spec/spec_helper.rb
CHANGED
@@ -12,6 +12,7 @@ module EprimeTestHelper
|
|
12
12
|
EXCEL_FILE = File.join(SAMPLE_DIR, 'excel_tsv.txt')
|
13
13
|
BAD_EXCEL_FILE = File.join(SAMPLE_DIR, 'bad_excel_tsv.txt')
|
14
14
|
EPRIME_FILE = File.join(SAMPLE_DIR, 'eprime_tsv.txt')
|
15
|
+
RAW_TSV_FILE = File.join(SAMPLE_DIR, 'raw_tsv.txt')
|
15
16
|
UNKNOWN_FILE = File.join(SAMPLE_DIR, 'unknown_type.txt')
|
16
17
|
UNREADABLE_FILE = File.join(SAMPLE_DIR, 'unreadable_file')
|
17
18
|
CORRUPT_LOG_FILE = File.join(SAMPLE_DIR, 'corrupt_log_file.txt')
|
@@ -103,4 +104,10 @@ module EprimeTestHelper
|
|
103
104
|
CONST_EXPRS[sym][1].call.to_s
|
104
105
|
end
|
105
106
|
|
107
|
+
def time
|
108
|
+
t1 = Time.now.to_f
|
109
|
+
yield
|
110
|
+
return Time.now.to_f - t1
|
111
|
+
end
|
112
|
+
|
106
113
|
end
|
@@ -9,6 +9,7 @@ require File.join(File.dirname(__FILE__),'../spec_helper')
|
|
9
9
|
require File.join(File.dirname(__FILE__), '../../lib/eprime')
|
10
10
|
|
11
11
|
require 'transformers/column_calculator'
|
12
|
+
require 'transformers/row_filter'
|
12
13
|
|
13
14
|
include EprimeTestHelper
|
14
15
|
|
@@ -19,8 +20,12 @@ shared_examples_for "Eprime::Transformers::ColumnCalculator with edata" do
|
|
19
20
|
@calc.size.should == @edata.size
|
20
21
|
end
|
21
22
|
|
23
|
+
it "should not be nil" do
|
24
|
+
@calc[0].should_not be_nil
|
25
|
+
end
|
26
|
+
|
22
27
|
it "should allow accessing rows" do
|
23
|
-
@calc[0].should
|
28
|
+
@calc[0].should be_a_kind_of(Eprime::Data::Row)
|
24
29
|
end
|
25
30
|
|
26
31
|
it "should return data" do
|
@@ -118,6 +123,17 @@ shared_examples_for "Eprime::Transformers::ColumnCalculator with edata" do
|
|
118
123
|
}.should raise_error(Eprime::Transformers::ColumnCalculator::ComputationError)
|
119
124
|
end
|
120
125
|
|
126
|
+
it "should work in a RowFilter" do
|
127
|
+
d = mock_edata
|
128
|
+
cc = Eprime::Transformers::ColumnCalculator.new
|
129
|
+
cc.data = d
|
130
|
+
cc.sort_expression = '1'
|
131
|
+
filtered = Eprime::Transformers::RowFilter.new(cc, lambda {|r| true})
|
132
|
+
filtered.each do |r|
|
133
|
+
r.should_not be_nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
121
137
|
end
|
122
138
|
|
123
139
|
describe Eprime::Transformers::ColumnCalculator do
|
@@ -139,31 +155,36 @@ describe Eprime::Transformers::ColumnCalculator do
|
|
139
155
|
|
140
156
|
it "should return data for data columns" do
|
141
157
|
@edata[0]['stim_time'].should_not be_nil
|
142
|
-
@calc[0]
|
158
|
+
@calc[0]['stim_time'].should == @edata[0]['stim_time']
|
143
159
|
end
|
144
160
|
|
145
161
|
it "should compute static columns on single rows" do
|
146
162
|
@calc.computed_column "always_1", "1"
|
147
|
-
@calc[0]
|
163
|
+
@calc[0]["always_1"].should == "1"
|
148
164
|
end
|
149
165
|
|
150
166
|
it "should compute on single rows with some math" do
|
151
167
|
@calc.computed_column "test", "(3+2)*4"
|
152
|
-
@calc[0]
|
168
|
+
@calc[0]["test"].should == ((3+2)*4).to_s
|
153
169
|
end
|
154
170
|
|
155
171
|
it "should allow adding two columns" do
|
156
172
|
@calc.computed_column "always_1", "1"
|
157
173
|
@calc.computed_column "test", "(3+2)*4"
|
158
|
-
@calc[0]
|
159
|
-
@calc[0]
|
174
|
+
@calc[0]["always_1"].should == "1"
|
175
|
+
@calc[0]["test"].should == ((3+2)*4).to_s
|
160
176
|
end
|
161
177
|
|
162
178
|
it "should raise when computing nonexistent column" do
|
163
179
|
lambda {
|
164
|
-
@calc[0]
|
180
|
+
@calc[0]['nonexistent']
|
165
181
|
}.should raise_error(IndexError)
|
166
182
|
end
|
183
|
+
|
184
|
+
it "should allow computing via lambda" do
|
185
|
+
@calc.computed_column "always_1", lambda {|r| "1"}
|
186
|
+
@calc[0]["always_1"].should == "1"
|
187
|
+
end
|
167
188
|
|
168
189
|
it "should compute constants via indexing" do
|
169
190
|
@calc.computed_column "always_1", "1"
|
@@ -198,6 +219,16 @@ describe Eprime::Transformers::ColumnCalculator do
|
|
198
219
|
end
|
199
220
|
end
|
200
221
|
|
222
|
+
it "should compute based on data columns by lambda" do
|
223
|
+
@calc.computed_column "stim_time_s", lambda { |row|
|
224
|
+
row['stim_time'].to_f/1000
|
225
|
+
}
|
226
|
+
@calc.columns.should include('stim_time_s')
|
227
|
+
@calc.each do |row|
|
228
|
+
row['stim_time_s'].should == row['stim_time'].to_f/1000
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
201
232
|
it "should allow math between two columns" do
|
202
233
|
@calc.computed_column "stim_from_run", "{stim_time}-{run_start}"
|
203
234
|
@calc.each do |row|
|
@@ -9,6 +9,7 @@ require File.join(File.dirname(__FILE__),'../spec_helper')
|
|
9
9
|
require File.join(File.dirname(__FILE__), '../../lib/eprime')
|
10
10
|
require 'transformers/multipasser'
|
11
11
|
|
12
|
+
include Eprime::Transformers
|
12
13
|
include EprimeTestHelper
|
13
14
|
|
14
15
|
describe Eprime::Transformers::Multipasser do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optimus-ep
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Vack
|
@@ -9,11 +9,12 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-09-02 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rparsec
|
17
|
+
type: :runtime
|
17
18
|
version_requirement:
|
18
19
|
version_requirements: !ruby/object:Gem::Requirement
|
19
20
|
requirements:
|
@@ -21,6 +22,16 @@ dependencies:
|
|
21
22
|
- !ruby/object:Gem::Version
|
22
23
|
version: "1.0"
|
23
24
|
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: hoe
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.7.0
|
34
|
+
version:
|
24
35
|
description: A collection of utilities for working with Eprime data files
|
25
36
|
email:
|
26
37
|
- njvack@freshforever.net
|
@@ -60,6 +71,7 @@ files:
|
|
60
71
|
- lib/eprimetab_parser.rb
|
61
72
|
- lib/excel_parser.rb
|
62
73
|
- lib/log_file_parser.rb
|
74
|
+
- lib/raw_tab_parser.rb
|
63
75
|
- lib/runners/generic_runner.rb
|
64
76
|
- lib/tabfile_parser.rb
|
65
77
|
- lib/tabfile_writer.rb
|
@@ -119,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
131
|
requirements: []
|
120
132
|
|
121
133
|
rubyforge_project: optimus-ep
|
122
|
-
rubygems_version: 1.0
|
134
|
+
rubygems_version: 1.2.0
|
123
135
|
signing_key:
|
124
136
|
specification_version: 2
|
125
137
|
summary: A collection of utilities for working with Eprime data files
|