scout-gear 7.2.0 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +37 -3
  3. data/VERSION +1 -1
  4. data/lib/scout/concurrent_stream.rb +9 -8
  5. data/lib/scout/exceptions.rb +1 -0
  6. data/lib/scout/log/color.rb +0 -1
  7. data/lib/scout/log/progress/util.rb +65 -0
  8. data/lib/scout/misc/helper.rb +31 -0
  9. data/lib/scout/misc/monitor.rb +1 -1
  10. data/lib/scout/misc.rb +1 -0
  11. data/lib/scout/open/stream.rb +21 -27
  12. data/lib/scout/persist.rb +42 -28
  13. data/lib/scout/semaphore.rb +8 -1
  14. data/lib/scout/tsv/dumper.rb +13 -8
  15. data/lib/scout/tsv/index.rb +127 -15
  16. data/lib/scout/tsv/open.rb +128 -0
  17. data/lib/scout/tsv/parser.rb +70 -43
  18. data/lib/scout/tsv/path.rb +4 -4
  19. data/lib/scout/tsv/persist/adapter.rb +52 -33
  20. data/lib/scout/tsv/persist/fix_width_table.rb +324 -0
  21. data/lib/scout/tsv/persist/serialize.rb +117 -0
  22. data/lib/scout/tsv/persist/tokyocabinet.rb +3 -3
  23. data/lib/scout/tsv/persist.rb +0 -2
  24. data/lib/scout/tsv/traverse.rb +130 -35
  25. data/lib/scout/tsv/util/filter.rb +303 -0
  26. data/lib/scout/tsv/util/process.rb +73 -0
  27. data/lib/scout/tsv/util/select.rb +220 -0
  28. data/lib/scout/tsv/util.rb +77 -19
  29. data/lib/scout/tsv.rb +2 -2
  30. data/lib/scout/work_queue/worker.rb +1 -1
  31. data/lib/scout/workflow/definition.rb +8 -0
  32. data/lib/scout/workflow/step/info.rb +4 -0
  33. data/lib/scout/workflow/step/progress.rb +14 -0
  34. data/lib/scout/workflow/step.rb +10 -5
  35. data/lib/scout/workflow/task.rb +8 -4
  36. data/lib/scout/workflow/usage.rb +2 -0
  37. data/scout-gear.gemspec +33 -10
  38. data/scout_commands/workflow/task +3 -2
  39. data/scout_commands/workflow/task_old +2 -2
  40. data/test/scout/open/test_stream.rb +1 -1
  41. data/test/scout/test_persist.rb +61 -0
  42. data/test/scout/test_tmpfile.rb +1 -1
  43. data/test/scout/test_tsv.rb +10 -1
  44. data/test/scout/test_work_queue.rb +1 -0
  45. data/test/scout/tsv/persist/test_adapter.rb +10 -0
  46. data/test/scout/tsv/persist/test_fix_width_table.rb +134 -0
  47. data/test/scout/tsv/test_index.rb +94 -2
  48. data/test/scout/tsv/test_open.rb +9 -0
  49. data/test/scout/tsv/test_parser.rb +28 -3
  50. data/test/scout/tsv/test_persist.rb +7 -0
  51. data/test/scout/tsv/test_traverse.rb +110 -3
  52. data/test/scout/tsv/test_util.rb +23 -0
  53. data/test/scout/tsv/util/test_filter.rb +188 -0
  54. data/test/scout/tsv/util/test_process.rb +47 -0
  55. data/test/scout/tsv/util/test_select.rb +44 -0
  56. data/test/scout/work_queue/test_worker.rb +63 -6
  57. data/test/scout/workflow/step/test_load.rb +3 -3
  58. data/test/scout/workflow/test_step.rb +10 -10
  59. data/test/test_helper.rb +3 -1
  60. metadata +19 -6
@@ -0,0 +1,303 @@
1
+ require 'set'
2
+ module Filtered
3
+
4
+ class FilterArray
5
+ attr_accessor :filters
6
+
7
+ def ids
8
+ ids = filters.inject(nil){|list,filter| list.nil? ? filter.ids.dup : Misc.merge_sorted_arrays(list, filter.ids.dup)}
9
+ end
10
+
11
+ def method_missing(name, *args)
12
+ filters.each do |filter|
13
+ filter.send(name, *args)
14
+ end
15
+ end
16
+ end
17
+
18
+ #{{{ FILTER
19
+
20
+ class Filter
21
+ attr_accessor :data, :match, :fieldnum, :value, :list, :unsaved
22
+ attr_accessor :persistence
23
+
24
+ def initialize(data, match, value, persistence = nil)
25
+ @data = data
26
+ @match = match
27
+ @value = value
28
+ @unsaved = []
29
+
30
+ case
31
+ when Hash === persistence
32
+ @persistence = persistence
33
+ when String === persistence
34
+ @persistence = begin
35
+ file = ScoutCabinet.open(persistence, true, "HDB")
36
+ TSV.setup file, :type => :list
37
+ file.extend TSVAdapter
38
+ file
39
+ end
40
+ @persistence.read
41
+ end
42
+
43
+ @list = nil
44
+ case
45
+ when @match == :key
46
+ @value = Set.new(@value)
47
+ class << self
48
+ self
49
+ end.class_eval <<-EOC
50
+ def match_entry(key, entry)
51
+ key == @value or (Set === @value and @value.include? key)
52
+ end
53
+ EOC
54
+ when @match.match(/field:(.*)/)
55
+ @fieldnum = data.identify_field $1
56
+ class << self
57
+ self
58
+ end.class_eval <<-EOC
59
+ def match_entry(key, entry)
60
+ value = entry[@fieldnum]
61
+ value == @value or (Array === value and value.include? @value)
62
+ end
63
+ EOC
64
+ else
65
+ raise "Unknown match: #{ @match }"
66
+ end
67
+ end
68
+
69
+ def key
70
+ case
71
+ when String === value
72
+ value
73
+ else
74
+ Marshal.dump(value)
75
+ end
76
+ end
77
+
78
+ def save(ids)
79
+ if persistence
80
+ persistence.write_and_close do
81
+ persistence[self.key] = ids
82
+ end
83
+ else
84
+ if @list.nil?
85
+ @list = ids
86
+ else
87
+ @list.replace ids
88
+ end
89
+ end
90
+ end
91
+
92
+ def update
93
+ ids = []
94
+
95
+ data.with_unnamed do
96
+ data.unfiltered_each do |key, entry|
97
+ ids << key if match_entry(key, entry)
98
+ end
99
+ end
100
+
101
+ save(ids.sort)
102
+ end
103
+
104
+ def saved
105
+ if persistence.nil?
106
+ return nil if list.nil?
107
+ list
108
+ else
109
+ return nil if not persistence.include?(self.key)
110
+ persistence.write_and_close do
111
+ persistence[self.key]
112
+ end
113
+ end
114
+ end
115
+
116
+ def add_unsaved
117
+ save(Misc.merge_sorted_arrays(unsaved.sort, saved || [])) if unsaved.any?
118
+ unsaved.clear
119
+ end
120
+
121
+ def ids
122
+ add_unsaved
123
+
124
+ list = saved
125
+ if list.nil?
126
+ update
127
+ list = saved
128
+ end
129
+ list
130
+ end
131
+
132
+ def add(id)
133
+ unsaved.push id
134
+ end
135
+
136
+ def clean
137
+ add_unsaved
138
+ if persistence and persistence.include? self.key
139
+ persistence.write_and_close do
140
+ persistence.delete self.key
141
+ end
142
+ else
143
+ @list = nil
144
+ end
145
+ end
146
+
147
+ def reset
148
+ add_unsaved
149
+ if persistence
150
+ persistence.write_and_close do
151
+ persistence.clear
152
+ end
153
+ else
154
+ @list = nil
155
+ end
156
+ end
157
+ end
158
+
159
+ #}}} FILTER
160
+
161
+ def self.extended(base)
162
+ if not base.respond_to? :unfiltered_set
163
+ class << base
164
+ attr_accessor :filter_dir, :filters
165
+
166
+ alias unfiltered_set []=
167
+ alias []= filtered_set
168
+
169
+ alias unfiltered_filename filename
170
+ alias filename filtered_filename
171
+
172
+ alias unfiltered_keys keys
173
+ alias keys filtered_keys
174
+
175
+ alias unfiltered_values values
176
+ alias values filtered_values
177
+
178
+ alias unfiltered_each each
179
+ alias each filtered_each
180
+
181
+ alias unfiltered_collect collect
182
+ alias collect filtered_collect
183
+
184
+ alias unfiltered_delete delete
185
+ alias delete filtered_delete
186
+ end
187
+ end
188
+ base.filters = []
189
+ end
190
+
191
+ def filtered_filename
192
+ if filters.empty?
193
+ unfiltered_filename
194
+ else
195
+ unfiltered_filename + ":Filtered[#{filters.collect{|f| [f.match, Array === f.value ? Misc.digest(:values => f.value) : f.value] * "="} * ", "}]"
196
+ end
197
+ end
198
+
199
+ def filtered_set(key, value, clean = false)
200
+ if filters.empty?
201
+ self.send(:unfiltered_set, key, value, clean)
202
+ else
203
+ filters.each do |filter|
204
+ filter.add key if filter.match_entry key, value
205
+ end
206
+ self.send(:unfiltered_set, key, value, clean)
207
+ end
208
+ end
209
+
210
+ def filtered_keys
211
+ if filters.empty?
212
+ self.send(:unfiltered_keys)
213
+ else
214
+ filters.inject(nil){|list,filter| list.nil? ? filter.ids.dup : Misc.intersect_sorted_arrays(list, filter.ids.dup)}
215
+ end
216
+ end
217
+
218
+ def filtered_values
219
+ if filters.empty?
220
+ self.send(:unfiltered_values)
221
+ else
222
+ ids = filters.inject(nil){|list,filter| list.nil? ? filter.ids.dup : Misc.intersect_sorted_arrays(list, filter.ids.dup)}
223
+ self.send :values_at, *ids
224
+ end
225
+ end
226
+
227
+ def filtered_each(&block)
228
+ if filters.empty?
229
+ self.send(:unfiltered_each, &block)
230
+ else
231
+ ids = filters.inject(nil){|list,filter| list.nil? ? filter.ids.dup : Misc.intersect_sorted_arrays(list, filter.ids.dup)}
232
+
233
+ ids.each do |id|
234
+ value = self[id]
235
+ yield id, value if block_given?
236
+ [id, value]
237
+ end
238
+ end
239
+ end
240
+
241
+ def filtered_collect(&block)
242
+ if filters.empty?
243
+ self.send(:unfiltered_collect, &block)
244
+ else
245
+ ids = filters.inject(nil){|list,filter| list = (list.nil? ? filter.ids.dup : Misc.intersect_sorted_arrays(list, filter.ids.dup))}
246
+
247
+ new = self.annotate({})
248
+
249
+ ids.zip(self.send(:values_at, *ids)).each do |id, values|
250
+ new[id] = values
251
+ end
252
+ new.send :collect, &block
253
+ end
254
+ end
255
+
256
+ def filtered_delete(key)
257
+ if filters.empty?
258
+ self.send(:unfiltered_delete, key)
259
+ else
260
+ reset_filters
261
+ self.send :unfiltered_delete, key
262
+ end
263
+ end
264
+
265
+ def add_filter(match, value, persistence = nil)
266
+ if persistence.nil? and filter_dir
267
+ persistence = File.join(filter_dir, match.to_s + '.filter')
268
+ end
269
+
270
+ filter = Filter.new self, match, value, persistence
271
+ filters.push filter
272
+ end
273
+
274
+ def pop_filter
275
+ filters.pop.add_unsaved if filters.any?
276
+ end
277
+
278
+ def size
279
+ filters.empty? ? super : filters.collect{|f| f.ids.length }.min
280
+ end
281
+
282
+ end
283
+
284
+ module TSV
285
+ def filter(filter_dir = nil)
286
+ self.extend Filtered
287
+ self.filter_dir = filter_dir
288
+ self.filters = []
289
+ self
290
+ end
291
+
292
+ def reset_filters
293
+ if @filter_dir.nil? or @filter_dir.empty?
294
+ @filters.each do |filter| filter.reset end if Array === @filters
295
+ return
296
+ end
297
+
298
+ Dir.glob(File.join(@filter_dir, '*.filter')).each do |f|
299
+ FileUtils.rm f
300
+ end
301
+ end
302
+ end
303
+
@@ -0,0 +1,73 @@
1
+ module TSV
2
+ def process(field, &block)
3
+ field_pos = identify_field field
4
+
5
+ through do |key, values|
6
+ case
7
+ when type == :single
8
+ field_values = values
9
+ when type == :flat
10
+ field_values = values
11
+ else
12
+ next if values.nil?
13
+ field_values = values[field_pos]
14
+ end
15
+
16
+ new_values = case
17
+ when block.arity == 1
18
+ yield(field_values)
19
+ when block.arity == 2
20
+ yield(field_values, key)
21
+ when block.arity == 3
22
+ yield(field_values, key, values)
23
+ else
24
+ raise "Unexpected arity in block, must be 1, 2 or 3: #{block.arity}"
25
+ end
26
+
27
+ case
28
+ when type == :single
29
+ self[key] = new_values
30
+ when type == :flat
31
+ self[key] = new_values
32
+ else
33
+ if ! values[field_pos].frozen? && ((String === values[field_pos] && String === new_values) ||
34
+ (Array === values[field_pos] && Array === new_values))
35
+ values[field_pos].replace new_values
36
+ else
37
+ values[field_pos] = new_values
38
+ end
39
+ self[key] = values
40
+ end
41
+ end
42
+
43
+ self
44
+ end
45
+
46
+ def add_field(name = nil)
47
+ through do |key, values|
48
+ new_values = yield(key, values)
49
+ new_values = [new_values] if type == :double and not Array === new_values
50
+
51
+ case
52
+ when (values.nil? and (fields.nil? or fields.empty?))
53
+ values = [new_values]
54
+ when values.nil?
55
+ values = [nil] * fields.length + [new_values]
56
+ when Array === values
57
+ values += [new_values]
58
+ else
59
+ values << new_values
60
+ end
61
+
62
+ self[key] = values
63
+ end
64
+
65
+ if not fields.nil? and not name.nil?
66
+ new_fields = self.fields + [name]
67
+ self.fields = new_fields
68
+ end
69
+
70
+ self
71
+ end
72
+
73
+ end
@@ -0,0 +1,220 @@
1
+ module TSV
2
+ def select(method = nil, invert = false, &block)
3
+ new = TSV.setup({}, :key_field => key_field, :fields => fields, :type => type, :filename => filename, :identifiers => identifiers)
4
+
5
+ self.annotate(new)
6
+
7
+ case
8
+ when (method.nil? and block_given?)
9
+ through do |key, values|
10
+ new[key] = values if invert ^ (yield key, values)
11
+ end
12
+ when Array === method
13
+ method = Set.new method
14
+ with_unnamed do
15
+ case type
16
+ when :single
17
+ through do |key, value|
18
+ new[key] = value if invert ^ (method.include? key or method.include? value)
19
+ end
20
+ when :list, :flat
21
+ through do |key, values|
22
+ new[key] = values if invert ^ (method.include? key or (method & values).length > 0)
23
+ end
24
+ else
25
+ through do |key, values|
26
+ new[key] = values if invert ^ (method.include? key or (method & values.flatten).length > 0)
27
+ end
28
+ end
29
+ end
30
+ when Regexp === method
31
+ with_unnamed do
32
+ through do |key, values|
33
+ new[key] = values if invert ^ ([key,values].flatten.select{|v| v =~ method}.any?)
34
+ end
35
+ end
36
+ when (String === method || Symbol === method)
37
+ if block_given?
38
+ case
39
+ when block.arity == 1
40
+ with_unnamed do
41
+ case
42
+ when (method == key_field or method == :key)
43
+ through do |key, values|
44
+ new[key] = values if invert ^ (yield(key))
45
+ end
46
+ when (type == :single or type == :flat)
47
+ through do |key, value|
48
+ new[key] = value if invert ^ (yield(value))
49
+ end
50
+ else
51
+ pos = identify_field method
52
+ raise "Field #{ method } not identified. Available: #{ fields * ", " }" if pos.nil?
53
+
54
+ through do |key, values|
55
+ new[key] = values if invert ^ (yield(values[pos]))
56
+ end
57
+ end
58
+ end
59
+ when block.arity == 2
60
+ with_unnamed do
61
+ case
62
+ when (method == key_field or method == :key)
63
+ through do |key, values|
64
+ new[key] = values if invert ^ (yield(key, key))
65
+ end
66
+ when (type == :single or type == :flat)
67
+ through do |key, value|
68
+ new[key] = value if invert ^ (yield(key, value))
69
+ end
70
+ else
71
+ pos = identify_field method
72
+ through do |key, values|
73
+ new[key] = values if invert ^ (yield(key, values[pos]))
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+
80
+ else
81
+ with_unnamed do
82
+ through do |key, values|
83
+ new[key] = values if invert ^ ([key,values].flatten.select{|v| v == method}.any?)
84
+ end
85
+ end
86
+ end
87
+ when Hash === method
88
+ key = method.keys.first
89
+ method = method.values.first
90
+ case
91
+ when (Array === method and (key == :key or key_field == key))
92
+ with_unnamed do
93
+ Annotated.purge(method).each{|key|
94
+ new[key] = self[key] if invert ^ (self.include? key)
95
+ }
96
+ end
97
+ when Array === method
98
+ with_unnamed do
99
+ method = Set.new method unless Set === method
100
+ case type
101
+ when :single
102
+ through :key, key do |key, value|
103
+ new[key] = self[key] if invert ^ (method.include? value)
104
+ end
105
+ when :list
106
+ through :key, key do |key, values|
107
+ new[key] = self[key] if invert ^ (method.include? values.first)
108
+ end
109
+ when :flat #untested
110
+ through :key, key do |key, values|
111
+ new[key] = self[key] if invert ^ ((method & values.flatten).any?)
112
+ end
113
+ else
114
+ through :key, key do |key, values|
115
+ new[key] = self[key] if invert ^ ((method & values.flatten).any?)
116
+ end
117
+ end
118
+ end
119
+
120
+ when Regexp === method
121
+ with_unnamed do
122
+ through :key, key do |key, values|
123
+ values = [values] if type == :single
124
+ new[key] = self[key] if invert ^ (values.flatten.select{|v| v =~ method}.any?)
125
+ end
126
+ end
127
+
128
+ when (String === method and method =~ /name:(.*)/)
129
+ name = $1
130
+ old_unnamed = self.unnamed
131
+ self.unnamed = false
132
+ if name.strip =~ /^\/(.*)\/$/
133
+ regexp = Regexp.new $1
134
+ through :key, key do |key, values|
135
+ case type
136
+ when :single
137
+ values = values.annotate([values])
138
+ when :double
139
+ values = values[0]
140
+ end
141
+ new[key] = self[key] if invert ^ (values.select{|v| v.name =~ regexp}.any?)
142
+ end
143
+ else
144
+ through :key, key do |key, values|
145
+ case type
146
+ when :single
147
+ values = values.annotate([values])
148
+ when :double
149
+ values = values[0]
150
+ end
151
+ new[key] = self[key] if invert ^ (values.select{|v| v.name == name}.any?)
152
+ end
153
+ end
154
+ self.unnamed = old_unnamed
155
+
156
+ when String === method
157
+ if method =~ /^([<>]=?)(.*)/
158
+ with_unnamed do
159
+ through :key, key do |key, values|
160
+ value = Array === values ? values.flatten.first : values
161
+ new[key] = self[key] if value.to_f.send($1, $2.to_f)
162
+ end
163
+ end
164
+ else
165
+ with_unnamed do
166
+ through :key, key do |key, values|
167
+ values = [values] if type == :single
168
+ new[key] = self[key] if invert ^ (values.flatten.select{|v| v == method}.length > 0)
169
+ end
170
+ end
171
+ end
172
+ when Numeric === method
173
+ with_unnamed do
174
+ through :key, key do |key, values|
175
+ new[key] = self[key] if invert ^ (values.flatten.length >= method)
176
+ end
177
+ end
178
+ when Proc === method
179
+ with_unnamed do
180
+ through :key, key do |key, values|
181
+ values = [values] if type == :single
182
+ new[key] = self[key] if invert ^ (values.flatten.select{|v| method.call(v)}.length > 0)
183
+ end
184
+ end
185
+ end
186
+ end
187
+ new
188
+ end
189
+
190
+ def reorder(key_field = nil, fields = nil, merge: true, one2one: :fill)
191
+ res = self.annotate({})
192
+ key_field_name, field_names = through key_field, fields, one2one: one2one do |k,v|
193
+ if @type == :double && merge && res.include?(k)
194
+ current = res[k]
195
+ if merge == :concat
196
+ v.each_with_index do |new,i|
197
+ next if new.empty?
198
+ current[i].concat(new)
199
+ end
200
+ else
201
+ merged = []
202
+ v.each_with_index do |new,i|
203
+ next if new.empty?
204
+ merged[i] = current[i] + new
205
+ end
206
+ res[k] = merged
207
+ end
208
+ else
209
+ res[k] = v
210
+ end
211
+ end
212
+ res.key_field = key_field_name
213
+ res.fields = field_names
214
+ res
215
+ end
216
+
217
+ def slice(fields)
218
+ reorder :key, fields
219
+ end
220
+ end
@@ -1,24 +1,82 @@
1
1
  #require_relative '../../../modules/rbbt-util/lib/rbbt/tsv/manipulate'
2
2
  #Log.warn "USING OLD RBBT CODE: #{__FILE__}"
3
+ require_relative 'traverse'
4
+ require_relative 'util/process'
5
+ require_relative 'util/select'
3
6
  module TSV
4
- #[:each, :collect, :map].each do |method|
5
- # define_method(method) do |*args,&block|
6
- # super(*args) do |k,v|
7
- # NamedArray.setup(v, @fields) unless @unnamed
8
- # block.call k, v
9
- # end
10
- # end
11
- #end
12
-
13
- #[:select, :reject].each do |method|
14
- # define_method(method) do |*args,&block|
15
- # res = super(*args) do |k,v|
16
- # NamedArray.setup(v, @fields) unless @unnamed
17
- # block.call k, v
18
- # end
19
- # self.annotate(res)
20
- # res
21
- # end
22
- #end
7
+ def [](*args)
8
+ v = super(*args)
9
+ NamedArray.setup(v, @fields) unless @unnamed || ! (Array === v)
10
+ v
11
+ end
23
12
 
13
+ def each(*args, &block)
14
+ if block_given?
15
+ super(*args) do |k,v|
16
+ NamedArray.setup(v, @fields) unless @unnamed || ! (Array === v)
17
+ block.call(k, v)
18
+ end
19
+ else
20
+ super(*args)
21
+ end
22
+ end
23
+
24
+ def collect(*args, &block)
25
+ if block_given?
26
+ res = []
27
+ each do |k,v|
28
+ res << yield(k, v)
29
+ end
30
+ res
31
+ else
32
+ super(*args)
33
+ end
34
+ end
35
+
36
+ def with_unnamed
37
+ begin
38
+ old_unnamed = unnamed
39
+ unnamed = true
40
+ yield
41
+ ensure
42
+ unnamed = old_unnamed
43
+ end
44
+ end
45
+
46
+ def summary
47
+ key = nil
48
+ values = nil
49
+ self.each do |k, v|
50
+ key = k
51
+ values = v
52
+ break
53
+ end
54
+
55
+ filename = @filename
56
+ filename = "No filename" if filename.nil? || filename.empty?
57
+ filename.find if Path === filename
58
+ filename = File.basename(filename) + " [" + File.basename(persistence_path) + "]" if respond_to?(:persistence_path) and persistence_path
59
+
60
+ with_unnamed do
61
+ <<-EOF
62
+ Filename = #{filename}
63
+ Key field = #{key_field || "*No key field*"}
64
+ Fields = #{fields ? Log.fingerprint(fields) : "*No field info*"}
65
+ Type = #{type}
66
+ Size = #{size}
67
+ namespace = #{Log.fingerprint namespace}
68
+ identifiers = #{Log.fingerprint identifiers}
69
+ Example:
70
+ - #{key} -- #{Log.fingerprint values }
71
+ EOF
72
+ end
73
+ end
74
+
75
+ def all_fields
76
+ [@key_field] + @fields
77
+ end
78
+
79
+ def fingerprint
80
+ "TSV:{"<< Log.fingerprint(self.all_fields|| []) << ";" << Log.fingerprint(self.keys) << "}"
81
+ end
24
82
  end
data/lib/scout/tsv.rb CHANGED
@@ -6,10 +6,11 @@ require_relative 'tsv/persist'
6
6
  require_relative 'tsv/index'
7
7
  require_relative 'tsv/path'
8
8
  require_relative 'tsv/traverse'
9
+ require_relative 'tsv/open'
9
10
 
10
11
  module TSV
11
12
  extend MetaExtension
12
- extension_attr :key_field, :fields, :type, :filename, :namespace, :unnamed
13
+ extension_attr :key_field, :fields, :type, :filename, :namespace, :unnamed, :identifiers
13
14
 
14
15
  def self.open(file, options = {})
15
16
  persist, type = IndiferentHash.process_options options, :persist, :persist_type, :persist => false, :persist_type => "HDB"
@@ -22,6 +23,5 @@ module TSV
22
23
  end
23
24
  end
24
25
  end
25
-
26
26
  end
27
27