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 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
@@ -1,6 +1,7 @@
1
1
  require 'rake'
2
2
  require 'spec/rake/spectask'
3
- require 'config/requirements'
3
+ #require 'config/requirements'
4
+ require 'hoe'
4
5
  require 'config/hoe' # setup Hoe + all gem configuration
5
6
 
6
7
  Dir['tasks/**/*.rake'].each { |rake| load rake }
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 unnamed
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("Error: Added column #{col_id} after specifying columns at init", index)
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 > num_index)
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]
@@ -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 generate output rows
43
- @skip_columns = {} # A hash of columns we *don't* want to add -- just define the strings
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
- frame.columns.each do |column, value|
71
- begin
72
- # Do a check for columns to skip -- this will happen in the case
73
- # where you have Procedure[Session] and Procedure[Task] -- we
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
- raise DamagedFileError.new("Last frame never closed in #{file.path}") if in_frame
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
- def columns
191
- my_data = @data.dup
192
- return my_data if @parent.nil?
193
- parent_data = @parent.columns
194
- parent_data.each do |key, val|
195
- if my_data.has_key?(key)
196
- @parser.skip_column(key)
197
- # Append a string like "[Session]" or "[Block]" to the key name
198
- my_data["#{key}[#{@parser.levels[@level]}]"] = my_data[key]
199
- my_data["#{key}[#{@parser.levels[@parent.level]}]"] = val
200
- else
201
- my_data[key] = parent_data[key]
202
- end
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 method_missing(meth, *args)
208
- @data.send meth, *args
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.is_a? Proc
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.is_a? Proc
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
@@ -2,7 +2,7 @@ module Eprime
2
2
  module VERSION
3
3
  MAJOR = 0
4
4
  MINOR = 6
5
- TINY = 5
5
+ TINY = 9
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -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.5
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: 2008-09-02 00:00:00 -05:00
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.7.0
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.2.0
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