flat_filer 0.0.18 → 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: aea5aa325cee0fd5897274b7783bd5d13c22390f
4
+ data.tar.gz: 11f6771b6caf2522a0955d4a938e9b48bc5416bb
5
+ SHA512:
6
+ metadata.gz: ee9378907c97fc2c5925581d65d8b03373f45bb2ff8f7491a99a5059bd93259b90a44047addd0b32bca9787cbdd71bb45993f0b421ec5f1d3e34043778c52a25
7
+ data.tar.gz: a378695c4b64871187d04ffac73c71e90ab2958599604b456def05864c172b4e777a85140134ac310c9fe1cc377588aa60ad8f163be6de820dd2aa9b05b7477b
@@ -25,13 +25,13 @@
25
25
  # puts "First Name: #{ person.first_name }"
26
26
  # puts "Last Name : #{ person.last_name}"
27
27
  # puts "Birthday : #{ person.birthday}"
28
- #
28
+ #
29
29
  # puts person.to_s
30
30
  # end
31
- #
32
- #
33
31
  #
34
- # An alternative method for adding fields is to pass a block to the
32
+ #
33
+ #
34
+ # An alternative method for adding fields is to pass a block to the
35
35
  # add_field method. The name is optional, but needs to be set either
36
36
  # by passing the name parameter, or in the block that's passed. When
37
37
  # you pass a block the first parameter is the FieldDef for the field
@@ -45,7 +45,7 @@
45
45
  # fd.add_formatter { |v| v.trim }
46
46
  # .
47
47
  # .
48
- # }
48
+ # }
49
49
  # end
50
50
  #
51
51
  # Filters and Formatters
@@ -59,10 +59,10 @@
59
59
  # Structurally, filters and formatters can be lambdas, code
60
60
  # blocks, or symbols referencing methods.
61
61
  #
62
- # There's an expectaiton on the part of formatters of the
62
+ # There's an expectaiton on the part of formatters of the
63
63
  # type of a field value. This means that the programmer
64
64
  # needs to set the value of a field as a type that the formatter
65
- # won't bork on.
65
+ # won't bork on.
66
66
  #
67
67
  # A good argument can be made to change filtering to happen any
68
68
  # time a field value is assigned. I've decided to not take this
@@ -102,432 +102,225 @@
102
102
  #
103
103
  # add_field also adds to a variable that olds a pack format. This is
104
104
  # how the records parsed and assembeled.
105
- require File.dirname(__FILE__) + "/../lib/core_extensions"
106
- class FlatFile
107
105
 
108
- class FlatFileException < Exception; end
109
- class ShortRecordError < FlatFileException; end
110
- class LongRecordError < FlatFileException; end
111
- class RecordLengthError < FlatFileException; end
112
-
113
- # A field definition tracks infomration that's necessary for
114
- # FlatFile to process a particular field. This is typically
115
- # added to a subclass of FlatFile like so:
116
- #
117
- # class SomeFile < FlatFile
118
- # add_field :some_field_name, :width => 35
119
- # end
120
- #
121
- class FieldDef
122
- attr :name, true
123
- attr :width, true
124
- attr :filters, true
125
- attr :formatters, true
126
- attr :file_klass, true
127
- attr :padding, true
128
- attr :map_in_proc, true
129
- attr :aggressive, true
130
- attr :default, true
131
-
132
- # Create a new FeildDef, having name and width. klass is a reference to the FlatFile
133
- # subclass that contains this field definition. This reference is needed when calling
134
- # filters if they are specified using a symbol.
135
- #
136
- # Options can be :padding (if present and a true value, field is marked as a pad field),
137
- # :width, specify the field width, :formatter, specify a formatter, :filter, specify a
138
- # filter.
139
- def initialize(name=null,options={},klass={})
140
- @name = name
141
- @width = 10
142
- @filters = Array.new
143
- @formatters = Array.new
144
- @file_klass = klass
145
- @padding = options.delete(:padding)
146
- @default = options.has_key?(:default) ? options.delete(:default) : ""
147
-
148
- add_filter(options[:filter]) if options.has_key?(:filter)
149
- add_formatter(options[:formatter]) if options.has_key?(:formatter)
150
- @map_in_proc = options[:map_in_proc]
151
- @width = options[:width] if options.has_key?(:width)
152
- @aggressive = options[:aggressive] || false
153
- end
154
-
155
- # Will return true if the field is a padding field. Padding fields are ignored
156
- # when doing various things. For example, when you're populating an ActiveRecord
157
- # model with a record, padding fields are ignored.
158
- def is_padding?
159
- @padding
160
- end
161
-
162
- # Add a filter. Filters are used for processing field data when a flat file is being
163
- # processed. For fomratting the data when writing a flat file, see add_formatter
164
- def add_filter(filter=nil,&block) #:nodoc:
165
- @filters.push(filter) unless filter.nil?
166
- @filters.push(block) if block_given?
167
- end
168
-
169
- # Add a formatter. Formatters are used for formatting a field
170
- # for rendering a record, or writing it to a file in the desired format.
171
- def add_formatter(formatter=nil,&block) #:nodoc:
172
- @formatters.push(formatter) if formatter
173
- @formatters.push(block) if block_given?
174
- end
175
-
176
- # Filters a value based on teh filters associated with a
177
- # FieldDef.
178
- def pass_through_filters(v) #:nodoc:
179
- pass_through(@filters,v)
180
- end
181
-
182
- # Filters a value based on the filters associated with a
183
- # FieldDef.
184
- def pass_through_formatters(v) #:nodoc:
185
- pass_through(@formatters,v)
186
- end
187
-
188
- #protected
189
-
190
- def pass_through(what,value) #:nodoc:
191
- #puts "PASS THROUGH #{what.inspect} => #{value}"
192
- what.each do |filter|
193
- value = case
194
- when filter.is_a?(Symbol)
195
- #puts "filter is a symbol"
196
- @file_klass.send(filter,value)
197
- when filter_block?(filter)
198
- #puts "filter is a block"
199
- filter.call(value)
200
- when filter_class?(filter)
201
- #puts "filter is a class"
202
- filter.filter(value)
203
- else
204
- #puts "filter not valid, preserving"
205
- value
206
- end
207
- end
208
- value
209
- end
210
-
211
- # Test to see if filter is a filter block. A filter block
212
- # can be called (using call) and takes one parameter
213
- def filter_block?(filter) #:nodoc:
214
- filter.respond_to?('call') && ( filter.arity >= 1 || filter.arity <= -1 )
215
- end
216
-
217
- # Test to see if a class is a filter class. A filter class responds
218
- # to the filter signal (you can call filter on it).
219
- def filter_class?(filter) #:nodoc:
220
- filter.respond_to?('filter')
221
- end
106
+ class FlatFile
107
+ class FlatFileException < Exception; end
108
+ class ShortRecordError < FlatFileException; end
109
+ class LongRecordError < FlatFileException; end
110
+ class RecordLengthError < FlatFileException; end
111
+
112
+
113
+ # A hash of data stored on behalf of subclasses. One hash
114
+ # key for each subclass.
115
+ @@subclass_data = Hash.new(nil)
116
+
117
+ # Used to generate unique names for pad fields which use :auto_name.
118
+ @@unique_id = 0
119
+
120
+ def next_record(io,&block)
121
+ return nil if io.eof?
122
+ required_line_length = self.class.get_subclass_variable 'width'
123
+ line = io.readline
124
+ line.chop!
125
+ return nil if line.length == 0
126
+ difference = required_line_length - line.length
127
+ raise RecordLengthError.new(
128
+ "length is #{line.length} but should be #{required_line_length}"
129
+ ) unless(difference == 0)
130
+
131
+ if block_given?
132
+ yield(create_record(line, io.lineno), line)
133
+ else
134
+ create_record(line,io.lineno)
222
135
  end
223
-
224
- # A record abstracts on line or 'record' of a fixed width field.
225
- # The methods available are the kes of the hash passed to the constructor.
226
- # For example the call:
227
- #
228
- # h = Hash['first_name','Andy','status','Supercool!']
229
- # r = Record.new(h)
230
- #
231
- # would respond to r.first_name, and r.status yielding
232
- # 'Andy' and 'Supercool!' respectively.
233
- #
234
- class Record
235
- attr_reader :fields
236
- attr_reader :klass
237
- attr_reader :line_number
238
-
239
- # Create a new Record from a hash of fields
240
- def initialize(klass,fields=Hash.new,line_number = -1,&block)
241
- @fields = Hash.new()
242
- @klass = klass
243
- @line_number = line_number
244
-
245
- klass_fields = klass.get_subclass_variable('fields')
246
-
247
- klass_fields.each do |f|
248
- @fields.store(f.name, "")
249
- end
250
-
251
- @fields.merge!(fields)
252
-
253
- @fields.each_key do |k|
254
- @fields.delete(k) unless klass.has_field?(k)
255
- end
256
-
257
- yield(block, self)if block_given?
258
-
259
- self
260
- end
261
-
262
- def map_in(model)
263
- @klass.non_pad_fields.each do |f|
264
- next unless(model.respond_to? "#{f.name}=")
265
- if f.map_in_proc
266
- f.map_in_proc.call(model,self)
267
- else
268
- model.send("#{f.name}=", send(f.name)) if f.aggressive or model.send(f.name).blank?
269
- model.send("#{f.name}=", f.default) if model.send(f.name).blank?
270
- end
271
- end
272
- end
273
-
274
- # Catches method calls and returns field values or raises an Error.
275
- def method_missing(method,params=nil)
276
- if(method.to_s.match(/^(.*)=$/))
277
- if(fields.has_key?($1.to_sym))
278
- @fields.store($1.to_sym,params)
279
- else
280
- raise Exception.new("Unknown method: #{ method }")
281
- end
282
- else
283
- if(fields.has_key? method)
284
- @fields.fetch(method)
285
- else
286
- raise Exception.new("Unknown method: #{ method }")
287
- end
288
- end
289
- end
290
-
291
- # Returns a string representation of the record suitable for writing to a flat
292
- # file on disk or other media. The fields are parepared according to the file
293
- # definition, and any formatters attached to the field definitions.
294
- def to_s
295
- klass.fields.map { |field_def|
296
- field_name = field_def.name.to_s
297
- v = @fields[ field_name.to_sym ].to_s
298
-
299
- field_def.pass_through_formatters(
300
- field_def.is_padding? ? "" : v
301
- )
302
- }.pack(klass.pack_format)
303
- end
304
-
305
- # Produces a multiline string, one field per line suitable for debugging purposes.
306
- def debug_string
307
- str = ""
308
- klass.fields.each do |f|
309
- if f.is_padding?
310
- str << "#{f.name}: \n"
311
- else
312
- str << "#{f.name}: #{send(f.name.to_sym)}\n"
313
- end
314
- end
315
-
316
- str
317
- end
136
+ end
137
+
138
+ # Iterate through each record (each line of the data file). The passed
139
+ # block is passed a new Record representing the line.
140
+ #
141
+ # s = SomeFile.new
142
+ # s.each_record(open('/path/to/file')) do |r|
143
+ # puts r.first_name
144
+ # end
145
+ #
146
+ def each_record(io,&block)
147
+ io.each_line do |line|
148
+ required_line_length = self.class.get_subclass_variable 'width'
149
+ #line = io.readline
150
+ line.chop!
151
+ next if line.length == 0
152
+ difference = required_line_length - line.length
153
+ raise RecordLengthError.new(
154
+ "length is #{line.length} but should be #{required_line_length}"
155
+ ) unless(difference == 0)
156
+ yield(create_record(line, io.lineno), line)
318
157
  end
319
-
320
- # A hash of data stored on behalf of subclasses. One hash
321
- # key for each subclass.
322
- @@subclass_data = Hash.new(nil)
323
-
324
- # Used to generate unique names for pad fields which use :auto_name.
325
- @@unique_id = 0
326
-
327
- def next_record(io,&block)
328
- return nil if io.eof?
329
- required_line_length = self.class.get_subclass_variable 'width'
330
- line = io.readline
331
- line.chop!
332
- return nil if line.length == 0
333
- difference = required_line_length - line.length
334
- raise RecordLengthError.new(
335
- "length is #{line.length} but should be #{required_line_length}"
336
- ) unless(difference == 0)
337
-
338
- if block_given?
339
- yield(create_record(line, io.lineno), line)
340
- else
341
- create_record(line,io.lineno)
342
- end
158
+ end
159
+
160
+ # create a record from line. The line is one line (or record) read from the
161
+ # text file. The resulting record is an object which. The object takes signals
162
+ # for each field according to the various fields defined with add_field or
163
+ # varients of it.
164
+ #
165
+ # line_number is an optional line number of the line in a file of records.
166
+ # If line is not in a series of records (lines), omit and it'll be -1 in the
167
+ # resulting record objects. Just make sure you realize this when reporting
168
+ # errors.
169
+ #
170
+ # Both a getter (field_name), and setter (field_name=) are available to the
171
+ # user.
172
+ def create_record(line, line_number = -1) #:nodoc:
173
+ h = Hash.new
174
+
175
+ pack_format = self.class.get_subclass_variable 'pack_format'
176
+ fields = self.class.get_subclass_variable 'fields'
177
+
178
+ f = line.unpack(pack_format)
179
+ (0..(fields.size-1)).map do |index|
180
+ unless fields[index].is_padding?
181
+ h.store fields[index].name, fields[index].pass_through_filters(f[index])
182
+ end
343
183
  end
344
-
345
- # Iterate through each record (each line of the data file). The passed
346
- # block is passed a new Record representing the line.
347
- #
348
- # s = SomeFile.new
349
- # s.each_record(open('/path/to/file')) do |r|
350
- # puts r.first_name
351
- # end
352
- #
353
- def each_record(io,&block)
354
- io.each_line do |line|
355
- required_line_length = self.class.get_subclass_variable 'width'
356
- #line = io.readline
357
- line.chop!
358
- next if line.length == 0
359
- difference = required_line_length - line.length
360
- raise RecordLengthError.new(
361
- "length is #{line.length} but should be #{required_line_length}"
362
- ) unless(difference == 0)
363
- yield(create_record(line, io.lineno), line)
364
- end
184
+ Record.new(self.class, h, line_number)
185
+ end
186
+
187
+ # Add a field to the FlatFile subclass. Options can include
188
+ #
189
+ # :width - number of characters in field (default 10)
190
+ # :filter - callack, lambda or code block for processing during reading
191
+ # :formatter - callback, lambda, or code block for processing during writing
192
+ #
193
+ # class SomeFile < FlatFile
194
+ # add_field :some_field_name, :width => 35
195
+ # end
196
+ #
197
+ def self.add_field(name=nil, options={},&block)
198
+ options[:width] ||= 10;
199
+
200
+ fields = get_subclass_variable 'fields'
201
+ width = get_subclass_variable 'width'
202
+ pack_format = get_subclass_variable 'pack_format'
203
+
204
+
205
+ fd = FieldDef.new(name,options,self)
206
+ yield(fd) if block_given?
207
+
208
+ fields << fd
209
+ width += fd.width
210
+ pack_format << "A#{fd.width}"
211
+ set_subclass_variable 'width', width
212
+ fd
213
+ end
214
+
215
+ # Add a pad field. To have the name auto generated, use :auto_name for
216
+ # the name parameter. For options see add_field.
217
+ def self.pad(name, options = {})
218
+ fd = self.add_field(
219
+ name.eql?(:auto_name) ? self.new_pad_name : name,
220
+ options
221
+ )
222
+ fd.padding = true
223
+ end
224
+
225
+ def self.new_pad_name #:nodoc:
226
+ "pad_#{ @@unique_id+=1 }".to_sym
227
+ end
228
+
229
+
230
+ # Create a new empty record object conforming to this file.
231
+ #
232
+ #
233
+ def self.new_record(model = nil, &block)
234
+ fields = get_subclass_variable 'fields'
235
+
236
+ record = Record.new(self)
237
+
238
+ fields.map do |f|
239
+ assign_method = "#{f.name}="
240
+ value = model.respond_to?(f.name.to_sym) ? model.send(f.name.to_sym) : ""
241
+ record.send(assign_method, value)
365
242
  end
366
243
 
367
- # create a record from line. The line is one line (or record) read from the
368
- # text file. The resulting record is an object which. The object takes signals
369
- # for each field according to the various fields defined with add_field or
370
- # varients of it.
371
- #
372
- # line_number is an optional line number of the line in a file of records.
373
- # If line is not in a series of records (lines), omit and it'll be -1 in the
374
- # resulting record objects. Just make sure you realize this when reporting
375
- # errors.
376
- #
377
- # Both a getter (field_name), and setter (field_name=) are available to the
378
- # user.
379
- def create_record(line, line_number = -1) #:nodoc:
380
- h = Hash.new
381
-
382
- pack_format = self.class.get_subclass_variable 'pack_format'
383
- fields = self.class.get_subclass_variable 'fields'
384
-
385
- f = line.unpack(pack_format)
386
- (0..(fields.size-1)).map do |index|
387
- unless fields[index].is_padding?
388
- h.store fields[index].name, fields[index].pass_through_filters(f[index])
389
- end
390
- end
391
- Record.new(self.class, h, line_number)
392
- end
393
-
394
- # Add a field to the FlatFile subclass. Options can include
395
- #
396
- # :width - number of characters in field (default 10)
397
- # :filter - callack, lambda or code block for processing during reading
398
- # :formatter - callback, lambda, or code block for processing during writing
399
- #
400
- # class SomeFile < FlatFile
401
- # add_field :some_field_name, :width => 35
402
- # end
403
- #
404
- def self.add_field(name=nil, options={},&block)
405
- options[:width] ||= 10;
406
-
407
- fields = get_subclass_variable 'fields'
408
- width = get_subclass_variable 'width'
409
- pack_format = get_subclass_variable 'pack_format'
410
-
411
-
412
- fd = FieldDef.new(name,options,self)
413
- yield(fd) if block_given?
414
-
415
- fields << fd
416
- width += fd.width
417
- pack_format << "A#{fd.width}"
418
- set_subclass_variable 'width', width
419
- fd
244
+ if block_given?
245
+ yield block, record
420
246
  end
421
247
 
422
- # Add a pad field. To have the name auto generated, use :auto_name for
423
- # the name parameter. For options see add_field.
424
- def self.pad(name, options = {})
425
- fd = self.add_field(
426
- name.eql?(:auto_name) ? self.new_pad_name : name,
427
- options
428
- )
429
- fd.padding = true
430
- end
248
+ record
249
+ end
431
250
 
432
- def self.new_pad_name #:nodoc:
433
- "pad_#{ @@unique_id+=1 }".to_sym
434
- end
251
+ # Return a lsit of fields for the FlatFile subclass
252
+ def fields
253
+ self.class.fields
254
+ end
435
255
 
256
+ def self.non_pad_fields
257
+ self.fields.select { |f| not f.is_padding? }
258
+ end
436
259
 
437
- # Create a new empty record object conforming to this file.
438
- #
439
- #
440
- def self.new_record(model = nil, &block)
441
- fields = get_subclass_variable 'fields'
442
-
443
- record = Record.new(self)
444
-
445
- fields.map do |f|
446
- assign_method = "#{f.name}="
447
- value = model.respond_to?(f.name.to_sym) ? model.send(f.name.to_sym) : ""
448
- record.send(assign_method, value)
449
- end
450
-
451
- if block_given?
452
- yield block, record
453
- end
454
-
455
- record
456
- end
260
+ def non_pad_fields
261
+ self.non_pad_fields
262
+ end
457
263
 
458
- # Return a lsit of fields for the FlatFile subclass
459
- def fields
460
- self.class.fields
461
- end
462
-
463
- def self.non_pad_fields
464
- self.fields.select { |f| not f.is_padding? }
465
- end
466
-
467
- def non_pad_fields
468
- self.non_pad_fields
469
- end
264
+ def self.fields
265
+ self.get_subclass_variable 'fields'
266
+ end
470
267
 
471
- def self.fields
472
- self.get_subclass_variable 'fields'
473
- end
474
-
475
- def self.has_field?(field_name)
476
-
477
- if self.fields.select { |f| f.name == field_name.to_sym }.length > 0
478
- true
479
- else
480
- false
481
- end
482
- end
268
+ def self.has_field?(field_name)
483
269
 
484
- def self.width
485
- get_subclass_variable 'width'
270
+ if self.fields.select { |f| f.name == field_name.to_sym }.length > 0
271
+ true
272
+ else
273
+ false
486
274
  end
275
+ end
487
276
 
488
- # Return the record length for the FlatFile subclass
489
- def width
490
- self.class.width
491
- end
492
-
493
- # Returns the pack format which is generated from add_field
494
- # calls. This format is used to unpack each line and create Records.
495
- def pack_format
496
- self.class.get_pack_format
497
- end
277
+ def self.width
278
+ get_subclass_variable 'width'
279
+ end
498
280
 
499
- def self.pack_format
500
- get_subclass_variable 'pack_format'
501
- end
281
+ # Return the record length for the FlatFile subclass
282
+ def width
283
+ self.class.width
284
+ end
502
285
 
503
- protected
286
+ # Returns the pack format which is generated from add_field
287
+ # calls. This format is used to unpack each line and create Records.
288
+ def pack_format
289
+ self.class.get_pack_format
290
+ end
504
291
 
505
- # Retrieve the subclass data hash for the current class
506
- def self.subclass_data #:nodoc:
507
- unless(@@subclass_data.has_key?(self))
508
- @@subclass_data.store(self, Hash.new)
509
- end
292
+ def self.pack_format
293
+ get_subclass_variable 'pack_format'
294
+ end
510
295
 
511
- @@subclass_data.fetch(self)
512
- end
296
+ protected
513
297
 
514
- # Retrieve a particular subclass variable for this class by it's name.
515
- def self.get_subclass_variable(name) #:nodoc:
516
- if subclass_data.has_key? name
517
- subclass_data.fetch name
518
- end
298
+ # Retrieve the subclass data hash for the current class
299
+ def self.subclass_data #:nodoc:
300
+ unless(@@subclass_data.has_key?(self))
301
+ @@subclass_data.store(self, Hash.new)
519
302
  end
520
303
 
521
- # Set a subclass variable of 'name' to 'value'
522
- def self.set_subclass_variable(name,value) #:nodoc:
523
- subclass_data.store name, value
524
- end
304
+ @@subclass_data.fetch(self)
305
+ end
525
306
 
526
- # Setup subclass class variables. This initializes the
527
- # record width, pack format, and fields array
528
- def self.inherited(s) #:nodoc:
529
- s.set_subclass_variable('width',0)
530
- s.set_subclass_variable('pack_format',"")
531
- s.set_subclass_variable('fields',Array.new)
307
+ # Retrieve a particular subclass variable for this class by it's name.
308
+ def self.get_subclass_variable(name) #:nodoc:
309
+ if subclass_data.has_key? name
310
+ subclass_data.fetch name
532
311
  end
312
+ end
313
+
314
+ # Set a subclass variable of 'name' to 'value'
315
+ def self.set_subclass_variable(name,value) #:nodoc:
316
+ subclass_data.store name, value
317
+ end
318
+
319
+ # Setup subclass class variables. This initializes the
320
+ # record width, pack format, and fields array
321
+ def self.inherited(s) #:nodoc:
322
+ s.set_subclass_variable('width',0)
323
+ s.set_subclass_variable('pack_format',"")
324
+ s.set_subclass_variable('fields',Array.new)
325
+ end
533
326
  end
@@ -0,0 +1,116 @@
1
+ class FlatFile
2
+ # A field definition tracks infomration that's necessary for
3
+ # FlatFile to process a particular field. This is typically
4
+ # added to a subclass of FlatFile like so:
5
+ #
6
+ # class SomeFile < FlatFile
7
+ # add_field :some_field_name, :width => 35
8
+ # end
9
+ #
10
+ class FieldDef
11
+ attr :name, true
12
+ attr :width, true
13
+ attr :filters, true
14
+ attr :formatters, true
15
+ attr :file_klass, true
16
+ attr :padding, true
17
+ attr :map_in_proc, true
18
+ attr :aggressive, true
19
+ attr :default, true
20
+
21
+ # Create a new FeildDef, having name and width. klass is a reference to the FlatFile
22
+ # subclass that contains this field definition. This reference is needed when calling
23
+ # filters if they are specified using a symbol.
24
+ #
25
+ # Options can be :padding (if present and a true value, field is marked as a pad field),
26
+ # :width, specify the field width, :formatter, specify a formatter, :filter, specify a
27
+ # filter.
28
+ #
29
+ # The option :default => 'value' may be used to specify a default value to be mapped
30
+ # into a model field provided the flat filer record is empty.
31
+
32
+ def initialize(name=null,options={},klass={})
33
+ @name = name
34
+ @width = 10
35
+ @filters = Array.new
36
+ @formatters = Array.new
37
+ @file_klass = klass
38
+ @padding = options.delete(:padding)
39
+ @default = options.has_key?(:default) ? options.delete(:default) : ""
40
+
41
+ add_filter(options[:filter]) if options.has_key?(:filter)
42
+ add_formatter(options[:formatter]) if options.has_key?(:formatter)
43
+ @map_in_proc = options[:map_in_proc]
44
+ @width = options[:width] if options.has_key?(:width)
45
+ @aggressive = options[:aggressive] || false
46
+ end
47
+
48
+ # Will return true if the field is a padding field. Padding fields are ignored
49
+ # when doing various things. For example, when you're populating an ActiveRecord
50
+ # model with a record, padding fields are ignored.
51
+ def is_padding?
52
+ @padding
53
+ end
54
+
55
+ # Add a filter. Filters are used for processing field data when a flat file is being
56
+ # processed. For fomratting the data when writing a flat file, see add_formatter
57
+ def add_filter(filter=nil,&block) #:nodoc:
58
+ @filters.push(filter) unless filter.nil?
59
+ @filters.push(block) if block_given?
60
+ end
61
+
62
+ # Add a formatter. Formatters are used for formatting a field
63
+ # for rendering a record, or writing it to a file in the desired format.
64
+ def add_formatter(formatter=nil,&block) #:nodoc:
65
+ @formatters.push(formatter) if formatter
66
+ @formatters.push(block) if block_given?
67
+ end
68
+
69
+ # Filters a value based on teh filters associated with a
70
+ # FieldDef.
71
+ def pass_through_filters(v) #:nodoc:
72
+ pass_through(@filters,v)
73
+ end
74
+
75
+ # Filters a value based on the filters associated with a
76
+ # FieldDef.
77
+ def pass_through_formatters(v) #:nodoc:
78
+ pass_through(@formatters,v)
79
+ end
80
+
81
+ #protected
82
+
83
+ def pass_through(what,value) #:nodoc:
84
+ #puts "PASS THROUGH #{what.inspect} => #{value}"
85
+ what.each do |filter|
86
+ value = case
87
+ when filter.is_a?(Symbol)
88
+ #puts "filter is a symbol"
89
+ @file_klass.send(filter,value)
90
+ when filter_block?(filter)
91
+ #puts "filter is a block"
92
+ filter.call(value)
93
+ when filter_class?(filter)
94
+ #puts "filter is a class"
95
+ filter.filter(value)
96
+ else
97
+ #puts "filter not valid, preserving"
98
+ value
99
+ end
100
+ end
101
+ value
102
+ end
103
+
104
+ # Test to see if filter is a filter block. A filter block
105
+ # can be called (using call) and takes one parameter
106
+ def filter_block?(filter) #:nodoc:
107
+ filter.respond_to?('call') && ( filter.arity >= 1 || filter.arity <= -1 )
108
+ end
109
+
110
+ # Test to see if a class is a filter class. A filter class responds
111
+ # to the filter signal (you can call filter on it).
112
+ def filter_class?(filter) #:nodoc:
113
+ filter.respond_to?('filter')
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,97 @@
1
+ class FlatFile
2
+ # A record abstracts on line or 'record' of a fixed width field.
3
+ # The methods available are the kes of the hash passed to the constructor.
4
+ # For example the call:
5
+ #
6
+ # h = Hash['first_name','Andy','status','Supercool!']
7
+ # r = Record.new(h)
8
+ #
9
+ # would respond to r.first_name, and r.status yielding
10
+ # 'Andy' and 'Supercool!' respectively.
11
+ #
12
+ class Record
13
+ attr_reader :fields
14
+ attr_reader :klass
15
+ attr_reader :line_number
16
+
17
+ # Create a new Record from a hash of fields
18
+ def initialize(klass,fields=Hash.new,line_number = -1,&block)
19
+ @fields = Hash.new()
20
+ @klass = klass
21
+ @line_number = line_number
22
+
23
+ klass_fields = klass.get_subclass_variable('fields')
24
+
25
+ klass_fields.each do |f|
26
+ @fields.store(f.name, "")
27
+ end
28
+
29
+ @fields.merge!(fields)
30
+
31
+ @fields.each_key do |k|
32
+ @fields.delete(k) unless klass.has_field?(k)
33
+ end
34
+
35
+ yield(block, self)if block_given?
36
+
37
+ self
38
+ end
39
+
40
+ def map_in(model)
41
+ @klass.non_pad_fields.each do |f|
42
+ next unless(model.respond_to? "#{f.name}=")
43
+ if f.map_in_proc
44
+ f.map_in_proc.call(model,self)
45
+ else
46
+ model.send("#{f.name}=", send(f.name)) if f.aggressive or model.send(f.name).blank?
47
+ model.send("#{f.name}=", f.default) if model.send(f.name).blank?
48
+ end
49
+ end
50
+ end
51
+
52
+ # Catches method calls and returns field values or raises an Error.
53
+ def method_missing(method,params=nil)
54
+ if(method.to_s.match(/^(.*)=$/))
55
+ if(fields.has_key?($1.to_sym))
56
+ @fields.store($1.to_sym,params)
57
+ else
58
+ raise Exception.new("Unknown method: #{ method }")
59
+ end
60
+ else
61
+ if(fields.has_key? method)
62
+ @fields.fetch(method)
63
+ else
64
+ raise Exception.new("Unknown method: #{ method }")
65
+ end
66
+ end
67
+ end
68
+
69
+ # Returns a string representation of the record suitable for writing to a flat
70
+ # file on disk or other media. The fields are parepared according to the file
71
+ # definition, and any formatters attached to the field definitions.
72
+ def to_s
73
+ klass.fields.map { |field_def|
74
+ field_name = field_def.name.to_s
75
+ v = @fields[ field_name.to_sym ].to_s
76
+
77
+ field_def.pass_through_formatters(
78
+ field_def.is_padding? ? "" : v
79
+ )
80
+ }.pack(klass.pack_format)
81
+ end
82
+
83
+ # Produces a multiline string, one field per line suitable for debugging purposes.
84
+ def debug_string
85
+ str = ""
86
+ klass.fields.each do |f|
87
+ if f.is_padding?
88
+ str << "#{f.name}: \n"
89
+ else
90
+ str << "#{f.name}: #{send(f.name.to_sym)}\n"
91
+ end
92
+ end
93
+
94
+ str
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,7 @@
1
+
2
+ require 'active_support/core_ext/object/blank'
3
+ require_relative './flat_file'
4
+ require_relative './flat_file/field_def'
5
+ require_relative './flat_file/record'
6
+
7
+
metadata CHANGED
@@ -1,56 +1,88 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: flat_filer
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.18
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
5
  platform: ruby
6
- authors:
6
+ authors:
7
7
  - Andrew Libby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
-
12
- date: 2009-09-25 00:00:00 -04:00
13
- default_executable:
14
- dependencies: []
15
-
11
+ date: 2018-02-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.11'
16
55
  description:
17
- email: alibby@tangeis.com
56
+ email: alibby@andylibby.org
18
57
  executables: []
19
-
20
58
  extensions: []
21
-
22
59
  extra_rdoc_files: []
23
-
24
- files:
25
- - lib/core_extensions.rb
60
+ files:
26
61
  - lib/flat_file.rb
27
- has_rdoc: true
28
- homepage: http://www.tangeis.com/
62
+ - lib/flat_file/field_def.rb
63
+ - lib/flat_file/record.rb
64
+ - lib/flat_filer.rb
65
+ homepage: https://github.com/alibby/flat_filer
29
66
  licenses: []
30
-
67
+ metadata: {}
31
68
  post_install_message:
32
69
  rdoc_options: []
33
-
34
- require_paths:
70
+ require_paths:
35
71
  - lib
36
- required_ruby_version: !ruby/object:Gem::Requirement
37
- requirements:
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
38
74
  - - ">="
39
- - !ruby/object:Gem::Version
40
- version: "0"
41
- version:
42
- required_rubygems_version: !ruby/object:Gem::Requirement
43
- requirements:
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
44
79
  - - ">="
45
- - !ruby/object:Gem::Version
46
- version: "0"
47
- version:
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
48
82
  requirements: []
49
-
50
83
  rubyforge_project:
51
- rubygems_version: 1.3.5
84
+ rubygems_version: 2.6.13
52
85
  signing_key:
53
- specification_version: 3
86
+ specification_version: 4
54
87
  summary: Library for processing flat files
55
88
  test_files: []
56
-
@@ -1,20 +0,0 @@
1
- class Object
2
- # An object is blank if it's nil, empty, or a whitespace string.
3
- # For example, "", " ", '0000', nil, [], 0, 0.0 and {} are blank.
4
- #
5
- # This simplifies
6
- # if !address.nil? && !address.empty?
7
- # to
8
- # if !address.blank?
9
- def blank?
10
- if respond_to?(:empty?) && respond_to?(:strip)
11
- empty? or strip.empty? or gsub('0', '').empty?
12
- elsif respond_to?(:empty?)
13
- empty?
14
- elsif respond_to?(:zero?)
15
- zero?
16
- else
17
- !self
18
- end
19
- end
20
- end