optimus-ep 0.6.5 → 0.6.9
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/History.txt +13 -0
- data/Rakefile +2 -1
- data/lib/eprime_data.rb +7 -8
- data/lib/log_file_parser.rb +113 -43
- data/lib/transformers/column_calculator.rb +5 -2
- data/lib/version.rb +1 -1
- data/spec/log_file_parser_spec.rb +59 -5
- data/spec/samples/optimus_log.txt +1 -1
- data/spec/transformers/column_calculator_spec.rb +12 -0
- metadata +4 -4
data/History.txt
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
== 0.6.9 2/25/2008
|
2
|
+
* New features:
|
3
|
+
* Supports parsing raw tab-delmimted files
|
4
|
+
* Allows Procs as starting values for ComputedColumns
|
5
|
+
* Bug fixes:
|
6
|
+
* No longer jumbles the order of Eprime log files
|
7
|
+
|
8
|
+
== 0.6.5 9/2/2008
|
9
|
+
* New features:
|
10
|
+
* Speed! Speed! Speed! Should be something like 10x faster.
|
11
|
+
* Procs are now allowed in ComputedColumns -- so if you want to do really
|
12
|
+
arbitrary things in your column computation, have at it.
|
13
|
+
|
1
14
|
== 0.6.0 7/14/2008
|
2
15
|
* New features:
|
3
16
|
* Added extract_timings, a script to pull stimulus timing data from eprime
|
data/Rakefile
CHANGED
data/lib/eprime_data.rb
CHANGED
@@ -115,10 +115,6 @@ module Eprime
|
|
115
115
|
return row
|
116
116
|
end
|
117
117
|
|
118
|
-
#def add_row_values(values, sort_value = 1)
|
119
|
-
# r = Row.new(self, values, sort_value)
|
120
|
-
#end
|
121
|
-
|
122
118
|
def find_column_index(col_id)
|
123
119
|
@column_hash[col_id]
|
124
120
|
end
|
@@ -126,8 +122,8 @@ module Eprime
|
|
126
122
|
def find_or_add_column_index(col_id)
|
127
123
|
index_id = find_column_index(col_id)
|
128
124
|
# If index_id was a string, nil means we may want to add it. If it's a
|
129
|
-
# numeric index, we want to return nil from here -- we're not gonna add
|
130
|
-
# indexes.
|
125
|
+
# numeric index, we want to return nil from here -- we're not gonna add
|
126
|
+
# unnamed indexes.
|
131
127
|
return index_id if index_id or col_id.is_a?(Fixnum)
|
132
128
|
# In this case, we're adding a column...
|
133
129
|
index = @columns.size
|
@@ -135,7 +131,10 @@ module Eprime
|
|
135
131
|
@column_hash[col_id] = index
|
136
132
|
@column_hash[index] = index
|
137
133
|
if @columns_set_in_initialize and not @options[:ignore_warnings]
|
138
|
-
raise ColumnAddedWarning.new(
|
134
|
+
raise ColumnAddedWarning.new(
|
135
|
+
"Error: Added column #{col_id} after specifying columns at init",
|
136
|
+
index
|
137
|
+
)
|
139
138
|
end
|
140
139
|
return index
|
141
140
|
end
|
@@ -159,7 +158,7 @@ module Eprime
|
|
159
158
|
|
160
159
|
def [](index)
|
161
160
|
num_index = @parent.find_column_index(index)
|
162
|
-
unless (num_index.is_a?(Fixnum) and @parent.columns.length
|
161
|
+
unless (num_index.is_a?(Fixnum) and @parent.columns.length>num_index)
|
163
162
|
raise IndexError.new("Column #{num_index} does not exist")
|
164
163
|
end
|
165
164
|
return @data[num_index]
|
data/lib/log_file_parser.rb
CHANGED
@@ -26,8 +26,6 @@ module Eprime
|
|
26
26
|
attr_reader :frames
|
27
27
|
attr_reader :levels
|
28
28
|
attr_reader :top_level
|
29
|
-
attr_reader :skip_columns
|
30
|
-
|
31
29
|
# Valid things for the options hash:
|
32
30
|
# :columns => an array of strings, predefining the expected columns
|
33
31
|
# (and their order)
|
@@ -39,8 +37,9 @@ module Eprime
|
|
39
37
|
@force = options[:force]
|
40
38
|
@file = file
|
41
39
|
@levels = [''] # The 0 index should be blank.
|
42
|
-
@top_level = 0 # This is the level of the frame that'll
|
43
|
-
|
40
|
+
@top_level = 0 # This is the level of the frame that'll
|
41
|
+
# generate output rows
|
42
|
+
@found_cols = ColumnList.new()
|
44
43
|
end
|
45
44
|
|
46
45
|
def make_frames!
|
@@ -56,30 +55,17 @@ module Eprime
|
|
56
55
|
make_frames!
|
57
56
|
end
|
58
57
|
rescue Exception => e
|
59
|
-
unless @force
|
60
|
-
raise e
|
61
|
-
end
|
62
|
-
end
|
63
|
-
if @columns
|
64
|
-
data = Eprime::Data.new(@columns)
|
65
|
-
else
|
66
|
-
data = Eprime::Data.new
|
58
|
+
raise e unless @force
|
67
59
|
end
|
60
|
+
|
61
|
+
@columns ||= @found_cols.names
|
62
|
+
data = Eprime::Data.new(@columns)
|
68
63
|
self.top_frames.each do |frame|
|
69
64
|
row = data.add_row
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
# shouldn't have Procedure, in that case.
|
75
|
-
unless @skip_columns[column]
|
76
|
-
row[column] = value
|
77
|
-
end
|
78
|
-
rescue Exception => e
|
79
|
-
unless @force
|
80
|
-
raise e
|
81
|
-
end
|
82
|
-
end
|
65
|
+
@found_cols.names_with_cols.each do |pair|
|
66
|
+
name, col = *pair
|
67
|
+
val = frame.get(col)
|
68
|
+
row[name] = val
|
83
69
|
end
|
84
70
|
end
|
85
71
|
return data
|
@@ -105,8 +91,9 @@ module Eprime
|
|
105
91
|
def frameify(file)
|
106
92
|
in_frame = false
|
107
93
|
frames = []
|
94
|
+
# This
|
108
95
|
frame = Frame.new(self)
|
109
|
-
level = 0
|
96
|
+
frame.level = 0
|
110
97
|
file.each_line do |line|
|
111
98
|
# TODO? Refactor this out into its own private function
|
112
99
|
l_s = line.strip
|
@@ -129,10 +116,15 @@ module Eprime
|
|
129
116
|
# One more special thing: Experiment gets renamed ExperimentName. WTF?
|
130
117
|
key = "ExperimentName" if key == "Experiment"
|
131
118
|
frame[key] = val
|
119
|
+
@found_cols.store(Column.new(key, frame.level))
|
132
120
|
end
|
133
121
|
end
|
134
122
|
end
|
135
|
-
|
123
|
+
if in_frame
|
124
|
+
raise DamagedFileError.new(
|
125
|
+
"Last frame never closed in #{file.path}"
|
126
|
+
)
|
127
|
+
end
|
136
128
|
return frames
|
137
129
|
end
|
138
130
|
|
@@ -148,6 +140,7 @@ module Eprime
|
|
148
140
|
end
|
149
141
|
else
|
150
142
|
if key == HEADER_END
|
143
|
+
@found_cols.levels = @levels
|
151
144
|
file.rewind
|
152
145
|
return # Get out of this function!
|
153
146
|
else
|
@@ -164,6 +157,7 @@ module Eprime
|
|
164
157
|
@frames.each do |frame|
|
165
158
|
counts[frame.level] += 1
|
166
159
|
key = @levels[frame.level]
|
160
|
+
@found_cols.store(Column.new(key, frame.level))
|
167
161
|
frame[key] = counts[frame.level]
|
168
162
|
counts.fill(0, (frame.level+1)..@levels.length)
|
169
163
|
end
|
@@ -178,6 +172,8 @@ module Eprime
|
|
178
172
|
end
|
179
173
|
|
180
174
|
class Frame
|
175
|
+
include Enumerable
|
176
|
+
|
181
177
|
attr_accessor :level
|
182
178
|
attr_accessor :parent
|
183
179
|
def initialize(parser)
|
@@ -187,25 +183,99 @@ module Eprime
|
|
187
183
|
@parser = parser
|
188
184
|
end
|
189
185
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
186
|
+
# Methods to make this behave hashlike. Don't just delegate to
|
187
|
+
# the @data hash; that's less clear.
|
188
|
+
def [](key)
|
189
|
+
return @data[Column.new(key, @level).to_s]
|
190
|
+
end
|
191
|
+
|
192
|
+
def []=(key, val)
|
193
|
+
@data[Column.new(key, @level).to_s] = val
|
194
|
+
end
|
195
|
+
|
196
|
+
def keys
|
197
|
+
@data.keys
|
198
|
+
end
|
199
|
+
|
200
|
+
def each
|
201
|
+
@data.each do |k, v|
|
202
|
+
yield k, v
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def get(col)
|
207
|
+
# If the value is supposed to be at our level, return it (nil is OK)
|
208
|
+
return @data[col.to_s] if col.level == @level
|
209
|
+
# If it could be in our parent, return that.
|
210
|
+
return @parent.get(col) if (@parent && col.level < @level)
|
211
|
+
# If that's not an option,
|
212
|
+
return nil
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
class ColumnList
|
217
|
+
attr_accessor :levels
|
218
|
+
def initialize(levels = [], cols = [])
|
219
|
+
@levels = levels
|
220
|
+
@cols = cols
|
221
|
+
@name_uses = Hash.new(0)
|
222
|
+
end
|
223
|
+
|
224
|
+
def store(col)
|
225
|
+
if (col.level >= @levels.length or col.level < 1)
|
226
|
+
raise IndexError.new(
|
227
|
+
"Level #{col.level} must be between 1 and #{@levels.length-1}")
|
228
|
+
end
|
229
|
+
if not @cols.include?(col)
|
230
|
+
@cols << col
|
231
|
+
@name_uses[col.name] += 1
|
203
232
|
end
|
204
|
-
return my_data
|
205
233
|
end
|
206
234
|
|
207
|
-
def
|
208
|
-
|
235
|
+
def names
|
236
|
+
return self.names_with_cols.map { |c|
|
237
|
+
c[0]
|
238
|
+
}
|
239
|
+
end
|
240
|
+
|
241
|
+
def names_with_cols
|
242
|
+
ncm = sorted_cols.map {|c| [
|
243
|
+
(@name_uses[c.name]==1) ?
|
244
|
+
c.name : "#{c.name}[#{@levels[c.level]}]",
|
245
|
+
c ]}
|
246
|
+
return ncm
|
247
|
+
end
|
248
|
+
|
249
|
+
def sorted_cols
|
250
|
+
cwi = []
|
251
|
+
@cols.each_with_index do |col, i|
|
252
|
+
cwi << [col, i]
|
253
|
+
end
|
254
|
+
return cwi.sort_by {|elem| [elem[0].level, elem[1]]}.map {|elem|
|
255
|
+
elem[0]
|
256
|
+
}
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
end
|
261
|
+
|
262
|
+
class Column
|
263
|
+
attr_accessor :name, :level
|
264
|
+
def initialize(name, level)
|
265
|
+
@name = name
|
266
|
+
@level = level
|
267
|
+
end
|
268
|
+
|
269
|
+
def ==(c)
|
270
|
+
@name == c.name and @level == c.level
|
271
|
+
end
|
272
|
+
|
273
|
+
def hash
|
274
|
+
return self.to_s.hash
|
275
|
+
end
|
276
|
+
|
277
|
+
def to_s
|
278
|
+
"#{@name}[#{@level}]"
|
209
279
|
end
|
210
280
|
end
|
211
281
|
end # Class LogfileParser
|
@@ -240,7 +240,7 @@ module Eprime
|
|
240
240
|
end
|
241
241
|
|
242
242
|
# Allow defining the column computation as a lambda
|
243
|
-
return @expression.expr.call(row) if @expression.expr.
|
243
|
+
return @expression.expr.call(row) if @expression.expr.respond_to? :call
|
244
244
|
|
245
245
|
|
246
246
|
column_names = @expression.columns
|
@@ -278,9 +278,12 @@ module Eprime
|
|
278
278
|
if @reset_when.call(row)
|
279
279
|
@current_value = @start_value
|
280
280
|
end
|
281
|
+
if @current_value.respond_to? :call
|
282
|
+
@current_value = @current_value.call(row)
|
283
|
+
end
|
281
284
|
|
282
285
|
if @count_when.call(row)
|
283
|
-
if @count_by.
|
286
|
+
if @count_by.respond_to? :call
|
284
287
|
@current_value = @count_by.call(@current_value)
|
285
288
|
elsif @count_by.is_a?(Symbol) || @count_by.is_a?(String)
|
286
289
|
@current_value = @current_value.send(@count_by)
|
data/lib/version.rb
CHANGED
@@ -66,7 +66,7 @@ describe Eprime::Reader::LogfileParser do
|
|
66
66
|
it "should have a parent in the first frame" do
|
67
67
|
@reader.frames.first.parent.should_not be_nil
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
describe "making eprime data" do
|
71
71
|
before :each do
|
72
72
|
@eprime = @reader.to_eprime
|
@@ -76,6 +76,11 @@ describe Eprime::Reader::LogfileParser do
|
|
76
76
|
@eprime.length.should == 3
|
77
77
|
end
|
78
78
|
|
79
|
+
it "should follow the column order in the example file" do
|
80
|
+
@eprime.columns[0].should == "ExperimentName"
|
81
|
+
@eprime.columns[1].should == "SessionDate"
|
82
|
+
end
|
83
|
+
|
79
84
|
it "should ignore extra colons in input data" do
|
80
85
|
@eprime.first['SessionTime'].should == '11:11:11'
|
81
86
|
end
|
@@ -88,10 +93,6 @@ describe Eprime::Reader::LogfileParser do
|
|
88
93
|
@eprime.columns.should_not include("CarriedVal")
|
89
94
|
end
|
90
95
|
|
91
|
-
it "should mark ambiguous columns for skip" do
|
92
|
-
@reader.skip_columns.should include("CarriedVal")
|
93
|
-
end
|
94
|
-
|
95
96
|
it "should include columns from level 2 and level 1 frames" do
|
96
97
|
@eprime.columns.should include("RandomSeed")
|
97
98
|
@eprime.columns.should include("BlockTitle")
|
@@ -153,4 +154,57 @@ describe Eprime::Reader::LogfileParser do
|
|
153
154
|
|
154
155
|
end
|
155
156
|
|
157
|
+
end
|
158
|
+
|
159
|
+
describe Eprime::Reader::LogfileParser::ColumnList do
|
160
|
+
before :each do
|
161
|
+
@cklass = Eprime::Reader::LogfileParser::Column
|
162
|
+
@levels = ['', 'Foo', 'Bar']
|
163
|
+
@list = Eprime::Reader::LogfileParser::ColumnList.new(@levels)
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should raise error when storing index 0" do
|
167
|
+
lambda {
|
168
|
+
@list.store(@cklass.new('test', 0))
|
169
|
+
}.should raise_error(IndexError)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should raise error when storing out of bounds" do
|
173
|
+
lambda {
|
174
|
+
@list.store(@cklass.new('test', @levels.length))
|
175
|
+
}.should raise_error(IndexError)
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should record and return names" do
|
179
|
+
@list.store(@cklass.new('test', 1))
|
180
|
+
@list.names.should == ['test']
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should not re-add repeated name at same level" do
|
184
|
+
@list.store(@cklass.new('test', 1))
|
185
|
+
@list.store(@cklass.new('test', 1))
|
186
|
+
@list.names.should == ['test']
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should record unique column names at different levels" do
|
190
|
+
@list.store(@cklass.new('test', 1))
|
191
|
+
@list.store(@cklass.new('another_test', 2))
|
192
|
+
@list.names.should == ['test', 'another_test']
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should add level names to repeated column names at different levels" do
|
196
|
+
@list.store(@cklass.new('test', 1))
|
197
|
+
@list.store(@cklass.new('test', 2))
|
198
|
+
@list.names.should == ['test[Foo]', 'test[Bar]']
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should return paired columns with names" do
|
202
|
+
col = @cklass.new('test', 1)
|
203
|
+
@list.store(col)
|
204
|
+
|
205
|
+
@list.names_with_cols.should == [
|
206
|
+
['test', col]
|
207
|
+
]
|
208
|
+
end
|
209
|
+
|
156
210
|
end
|
@@ -94,7 +94,6 @@ Display.RefreshRate: 60.000
|
|
94
94
|
*** LogFrame End ***
|
95
95
|
Level: 1
|
96
96
|
*** LogFrame Start ***
|
97
|
-
CarriedVal: SessionLevel
|
98
97
|
Experiment: optimus_test
|
99
98
|
SessionDate: 03-15-2008
|
100
99
|
SessionTime: 11:11:11
|
@@ -105,6 +104,7 @@ Session: 1
|
|
105
104
|
Display.RefreshRate: 60.000
|
106
105
|
Clock.Scale: 1
|
107
106
|
NumPeriods: 3
|
107
|
+
CarriedVal: SessionLevel
|
108
108
|
PeriodA: 10000
|
109
109
|
PeriodB: 10000
|
110
110
|
*** LogFrame End ***
|
@@ -395,6 +395,18 @@ describe Eprime::Transformers::ColumnCalculator do
|
|
395
395
|
row['reset_on_sparse'].should == i
|
396
396
|
end
|
397
397
|
end
|
398
|
+
|
399
|
+
it "should accept a Proc as a :start_value" do
|
400
|
+
@calc.counter_column "scan_delay", :start_value => lambda {|row|
|
401
|
+
row['stim_time'].to_i - row['run_start'].to_i
|
402
|
+
}
|
403
|
+
sd = @calc[0]['scan_delay']
|
404
|
+
i = 0
|
405
|
+
@calc.each do |row|
|
406
|
+
row['scan_delay'].should == sd+i
|
407
|
+
i += 1
|
408
|
+
end
|
409
|
+
end
|
398
410
|
end
|
399
411
|
end
|
400
412
|
|
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.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Vack
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-02-25 00:00:00 -06:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -30,7 +30,7 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.
|
33
|
+
version: 1.8.2
|
34
34
|
version:
|
35
35
|
description: A collection of utilities for working with Eprime data files
|
36
36
|
email:
|
@@ -131,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
131
|
requirements: []
|
132
132
|
|
133
133
|
rubyforge_project: optimus-ep
|
134
|
-
rubygems_version: 1.
|
134
|
+
rubygems_version: 1.3.1
|
135
135
|
signing_key:
|
136
136
|
specification_version: 2
|
137
137
|
summary: A collection of utilities for working with Eprime data files
|