hflr 0.10.1

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,4 @@
1
+ == 1.0.0 / 2009-08-03
2
+
3
+ * 1 major enhancement
4
+ * Birthday!
@@ -0,0 +1,45 @@
1
+ = HFLR
2
+
3
+ * http://ruff.rubyforge.org
4
+
5
+ == Description:
6
+
7
+ HFLR -- Hierarchical Fixed Length Records
8
+
9
+ Allows you to read and write files of fixed width records when the file contains one or more
10
+ than one type of record.
11
+
12
+ Install with 'gem install hflr'
13
+
14
+
15
+
16
+
17
+ See the tests and examples bundled with this gem.
18
+
19
+
20
+
21
+
22
+ == LICENSE:
23
+
24
+ (The MIT License)
25
+
26
+ Copyright (c) 2009 Colin C. Davis
27
+
28
+ Permission is hereby granted, free of charge, to any person obtaining
29
+ a copy of this software and associated documentation files (the
30
+ 'Software'), to deal in the Software without restriction, including
31
+ without limitation the rights to use, copy, modify, merge, publish,
32
+ distribute, sublicense, and/or sell copies of the Software, and to
33
+ permit persons to whom the Software is furnished to do so, subject to
34
+ the following conditions:
35
+
36
+ The above copyright notice and this permission notice shall be
37
+ included in all copies or substantial portions of the Software.
38
+
39
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
40
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
41
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
42
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
43
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
44
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
45
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,30 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ begin
6
+ require 'bones'
7
+ Bones.setup
8
+ rescue LoadError
9
+ begin
10
+ load 'tasks/setup.rb'
11
+ rescue LoadError
12
+ raise RuntimeError, '### please install the "bones" gem ###'
13
+ end
14
+ end
15
+
16
+ ensure_in_path 'lib'
17
+ require 'hflr'
18
+
19
+ task :default => 'spec:run'
20
+
21
+ PROJ.name = 'hflr'
22
+ PROJ.authors = 'Colin Davis'
23
+ PROJ.email = 'colin.c.davis@gmail.com'
24
+ PROJ.url = 'http://ruff.rubyforge.org'
25
+ PROJ.version = Hflr::VERSION
26
+ PROJ.rubyforge.name = 'hflr'
27
+
28
+ PROJ.spec.opts << '--color'
29
+
30
+ # EOF
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(
4
+ File.join(File.dirname(__FILE__), %w[.. lib hflr]))
5
+
6
+ # Put your code here
7
+
8
+ # EOF
@@ -0,0 +1,43 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{hflr}
5
+ s.version = "1.0.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Colin Davis"]
9
+ s.date = %q{2009-08-03}
10
+ s.default_executable = %q{hflr}
11
+ s.description = %q{HFLR -- Hierarchical Fixed Length Records
12
+
13
+ Allows you to read and write files of fixed width records when the file contains one or more
14
+ than one type of record.
15
+
16
+ Install with 'gem install hflr'
17
+
18
+ See the tests and examples bundled with this gem.}
19
+ s.email = %q{colin.c.davis@gmail.com}
20
+ s.executables = ["hflr"]
21
+ s.extra_rdoc_files = ["History.txt", "README.txt", "bin/hflr"]
22
+ s.files = ["History.txt", "README.txt", "Rakefile", "bin/hflr", "lib/hflr.rb", "lib/hflr/fl_record_file.rb", "lib/hflr/hflr.rb", "lib/hflr/record_template.rb", "sample2_out.dat", "spec/hflr_spec.rb", "spec/spec_helper.rb", "test/customer_orders.dat", "test/customers.dat", "test/examples.rb", "test/fixed_length_record_file_test.rb", "test/record_template_test.rb", "test/sample.dat", "test/sample2_out.dat", "test/sample_activities.dat", "test/sample_out.dat", "test/test_helper.rb", "test/test_hflr.rb"]
23
+ s.homepage = %q{http://ruff.rubyforge.org}
24
+ s.rdoc_options = ["--main", "README.txt"]
25
+ s.require_paths = ["lib"]
26
+ s.rubyforge_project = %q{hflr}
27
+ s.rubygems_version = %q{1.3.4}
28
+ s.summary = %q{HFLR -- Hierarchical Fixed Length Records Allows you to read and write files of fixed width records when the file contains one or more than one type of record}
29
+ s.test_files = ["test/test_hflr.rb", "test/test_helper.rb"]
30
+
31
+ if s.respond_to? :specification_version then
32
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
33
+ s.specification_version = 3
34
+
35
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
36
+ s.add_development_dependency(%q<bones>, [">= 2.5.1"])
37
+ else
38
+ s.add_dependency(%q<bones>, [">= 2.5.1"])
39
+ end
40
+ else
41
+ s.add_dependency(%q<bones>, [">= 2.5.1"])
42
+ end
43
+ end
@@ -0,0 +1,49 @@
1
+
2
+ module Hflr
3
+
4
+ # :stopdoc:
5
+ VERSION = '0.10.1'
6
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
+ # :startdoc:
9
+
10
+ # Returns the version string for the library.
11
+ #
12
+ def self.version
13
+ VERSION
14
+ end
15
+
16
+ # Returns the library path for the module. If any arguments are given,
17
+ # they will be joined to the end of the libray path using
18
+ # <tt>File.join</tt>.
19
+ #
20
+ def self.libpath( *args )
21
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
22
+ end
23
+
24
+ # Returns the lpath for the module. If any arguments are given,
25
+ # they will be joined to the end of the path using
26
+ # <tt>File.join</tt>.
27
+ #
28
+ def self.path( *args )
29
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
30
+ end
31
+
32
+ # Utility method used to require all files ending in .rb that lie in the
33
+ # directory below this file that has the same name as the filename passed
34
+ # in. Optionally, a specific _directory_ name can be passed in such that
35
+ # the _filename_ does not have to be equivalent to the directory.
36
+ #
37
+ def self.require_all_libs_relative_to( fname, dir = nil )
38
+ dir ||= ::File.basename(fname, '.*')
39
+ search_me = ::File.expand_path(
40
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
41
+
42
+ Dir.glob(search_me).sort.each {|rb| require rb}
43
+ end
44
+
45
+ end # module Hflr
46
+
47
+ Hflr.require_all_libs_relative_to(__FILE__)
48
+
49
+ # EOF
@@ -0,0 +1,104 @@
1
+
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + "/record_template")
4
+
5
+
6
+
7
+ class FixedLengthRecordFile
8
+
9
+ include Enumerable
10
+
11
+ attr_reader :line_number, :record_template
12
+
13
+ def initialize(source, record_types, record_layouts, logical_first_column=0, extra_columns = nil)
14
+ # Allow record layouts like
15
+ # {:type1=>[:var1=>1..5,:var2=>7..8],:type2=>[:var1=>1..1,:var2=>3..4]}
16
+ # ... todo
17
+ @line_number = 0
18
+ @file = source
19
+ @record_type_labels=record_types
20
+ @record_type_symbols = record_types.is_a?(Hash) ? record_types.invert : :none
21
+ if extra_columns then
22
+ @record_template = HFLR::RecordTemplate.create(record_layouts, @record_type_symbols, logical_first_column, extra_columns)
23
+ else
24
+ @record_template = HFLR::RecordTemplate.create(record_layouts, @record_type_symbols, logical_first_column)
25
+ end
26
+
27
+ end
28
+
29
+ def finished?
30
+ @file.eof?
31
+ end
32
+
33
+ def close
34
+ @file.close
35
+ end
36
+
37
+ # If multiple record types, extract it from the string, otherwise just return the type of this file
38
+ def get_record_type(line)
39
+ return nil if line.nil?
40
+ return nil if line.strip.empty?
41
+ @record_type_labels.is_a?(Hash) ? @record_type_labels[line[0..0]] : @record_type_labels
42
+ end
43
+
44
+ def build_record(line)
45
+ return nil if line.nil?
46
+
47
+ record_type = line_type(line)
48
+ raise "Unknown record type at line #{@line_number.to_s}" if record_type == :unknown
49
+ return @record_template[record_type].build_record(line.chomp)
50
+ end
51
+
52
+ def next_record
53
+ @line_number += 1
54
+ build_record(get_next_known_line_type)
55
+ end
56
+
57
+ def line_type(line)
58
+ record_type = get_record_type(line)
59
+ return record_type ? record_type : :unknown
60
+ end
61
+
62
+ def get_next_known_line_type
63
+ line = @file.gets
64
+ while line_type(line) == :unknown and (not finished?)
65
+ line = @file.gets
66
+ end
67
+ return line
68
+ end
69
+
70
+ def each
71
+ @file.each_line do |line|
72
+ @line_number += 1
73
+ unless line_type(line) == :unknown
74
+ data = build_record(line)
75
+ yield data
76
+ end
77
+ end
78
+ end
79
+
80
+ # This will take a Hash or Struct orArray; if an Array the record type must be the last element
81
+ def <<(record)
82
+ if record.is_a? Array
83
+ @file.puts @record_template[record.last].build_line(record)
84
+ else
85
+ if @record_template[record[:record_type]] == nil then
86
+ raise "Record type problem in output: #{record[:record_type].to_s} type on record, #{@record_template.keys.join(",")} types of templates"
87
+ end
88
+
89
+ @file.puts @record_template[record[:record_type]].build_line(record)
90
+ end
91
+ end
92
+
93
+ # Use when creating a new HFLR file
94
+ def self.open(path, mode, record_types, record_layouts, logical_first_column=0)
95
+ file = File.open(path, mode)
96
+ begin
97
+ hflr_file = new(file, record_types, record_layouts, logical_first_column)
98
+ yield hflr_file
99
+ ensure
100
+ file.close
101
+ end
102
+ end
103
+
104
+ end
@@ -0,0 +1,2 @@
1
+
2
+ require File.expand_path(File.dirname(__FILE__) + "/fl_record_file")
@@ -0,0 +1,150 @@
1
+
2
+ module HFLR
3
+
4
+ class RecordTemplate
5
+ UnfilledChar = ' '
6
+ MissingOutput = "ZZZZZZZZZZZZZZZZZZZZZ"
7
+
8
+ attr_reader :record_structure, :field_pattern,:record_type,:record_type_label
9
+ attr_accessor :strip_whitespace
10
+
11
+ def initialize(record_type, record_type_label, record_structure,field_pattern, field_widths)
12
+ @record_type = record_type
13
+ @record_type_label = record_type_label
14
+ @record_structure = record_structure
15
+ @field_pattern = field_pattern
16
+ @field_widths = field_widths
17
+ end
18
+
19
+ # Layouts is a hash of variables by record type
20
+ # record_type_symbols maps record type names to their labels in the data {:household=>"H",:person=>"P"}
21
+ # Returns a set of record templates, one for each record type
22
+ def self.create(record_layouts, record_type_symbols, first_column_location, extra_columns=[])
23
+ extra_columns = empty_extra_columns(record_layouts.keys) if extra_columns.is_a? Array
24
+ templates = {}
25
+ self.check_record_layouts(record_layouts)
26
+
27
+ record_layouts.keys.each do |record_type|
28
+ record_label = record_type_symbols == :none ? :none : record_type_symbols[record_type]
29
+ templates[record_type] =
30
+ self.create_template_class(record_type,
31
+ record_label,
32
+ record_layouts[record_type],
33
+ first_column_location,
34
+ extra_columns[record_type])
35
+ end
36
+ return templates
37
+ end
38
+
39
+
40
+ # If the name exists already do not replace it, but add extra columns not to be mapped by the unpack field patterns
41
+ # and ensure the record_type variable is added.
42
+ # Since 'record_type' may not be in the metadata we don't want to map it to a
43
+ # specific column location but do want it included always.
44
+ def self.add_extra_columns(names, extra)
45
+ new_names = names.dup
46
+ # names are not case sensitive
47
+ extra.each{|n|new_names << n unless names.map{|m| m.to_s.upcase}.include? n.to_s.upcase}
48
+
49
+ # No matter what, include 'record_type'
50
+ unless new_names.map{|n| n.to_s.upcase}.include?("RECORD_TYPE")
51
+ new_names << :record_type
52
+ end
53
+ return new_names
54
+ end
55
+
56
+ def self.get_pattern(layout, first_column_location=0)
57
+
58
+
59
+ layout.map {|l| '@' + (l.start - first_column_location).to_s + 'A' + l.len.to_s}.to_s
60
+ end
61
+
62
+ def build_record(line)
63
+ rec = line.unpack(@field_pattern)
64
+ rec.map{|f| f.strip!} if @strip_whitespace
65
+ begin
66
+ data = self.record_structure.new(*rec)
67
+ data[:record_type] = @record_type
68
+ rescue Exception=>msg
69
+ raise "On record type #{self.record_type} problem with structure " + msg.to_s
70
+ end
71
+ return data
72
+ end
73
+
74
+ def build_line(record)
75
+ line = format_fields(record).pack(@field_pattern)
76
+ line[0] = @record_type_label unless @record_type_label == :none
77
+ line.tr!("\0",UnfilledChar)
78
+ return line
79
+ end
80
+
81
+ private
82
+
83
+ def self.empty_extra_columns(record_types)
84
+ extra = {}
85
+ record_types.map{|rt| extra[rt] = []}
86
+ extra
87
+ end
88
+
89
+ # All starting columns must be in order
90
+ def self.check_record_layouts(layouts)
91
+ layouts.values.each do |layout|
92
+ last_v = layout.first
93
+ layout.each do |v|
94
+ if v.respond_to?(:rectype) then
95
+ if last_v.rectype != v.rectype
96
+ raise "record type mismatch between #{v.name} and #{last_v.name}"
97
+ end
98
+ end
99
+ if last_v.start<= v.start then
100
+ last_v = v
101
+ else
102
+ raise "Problem with start columns #{last_v.name} start #{last_v.start.to_s} out of sequence with #{v.name} starting at #{v.start.to_s}"
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ def self.create_template_class(record_type, record_type_label, layout, first_column_location, extra_columns = nil)
109
+ names = layout.map {|l| l.name.to_sym}
110
+ names = add_extra_columns(names, extra_columns)
111
+ structure = Struct.new(*names)
112
+ return new(record_type,
113
+ record_type_label,
114
+ structure,
115
+ self.get_pattern(layout, first_column_location),
116
+ layout.map{|v| v.len})
117
+ end
118
+
119
+
120
+ def format_fields(record)
121
+ if record.is_a?(Array) or record.is_a?(Struct) then
122
+ fields = []
123
+ @field_widths.each_with_index do |width, i|
124
+ fields << right_format(record[i], width)
125
+ end
126
+ return fields
127
+ else
128
+ raise "Record to format must be a Struct or Array"
129
+ end
130
+ end
131
+
132
+ def right_format(data, len)
133
+ data_str = ""
134
+ if data.is_a? String
135
+ data_str = data.ljust(len)
136
+ elsif data.is_a? Symbol
137
+ data_str = data.to_s.ljust(len)
138
+ else
139
+ data_str = sprintf("%0#{len.to_s}d",data)
140
+ data_str = MissingOutput[0..len-1] if data == -999998
141
+ end
142
+ raise "Data too large for allocated columns #{data_str}" if data_str.size > len
143
+ data_str
144
+ end
145
+
146
+
147
+ end # RecordTemplate class
148
+
149
+ end # HFLR module
150
+
@@ -0,0 +1 @@
1
+ joe 025