fixed_width 0.2.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,81 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe FixedWidth::Definition do
4
+ before(:each) do
5
+ end
6
+
7
+ describe "when specifying alignment" do
8
+ it "should have an alignment option" do
9
+ d = FixedWidth::Definition.new :align => :right
10
+ d.options[:align].should == :right
11
+ end
12
+
13
+ it "should default to being right aligned" do
14
+ d = FixedWidth::Definition.new
15
+ d.options[:align].should == :right
16
+ end
17
+
18
+ it "should override the default if :align is passed to the section" do
19
+ section = mock('section', :null_object => true)
20
+ FixedWidth::Section.should_receive(:new).with('name', {:align => :left}).and_return(section)
21
+ d = FixedWidth::Definition.new
22
+ d.options[:align].should == :right
23
+ d.section('name', :align => :left) {}
24
+ end
25
+ end
26
+
27
+ describe "when creating a section" do
28
+ before(:each) do
29
+ @d = FixedWidth::Definition.new
30
+ @section = mock('section', :null_object => true)
31
+ end
32
+
33
+ it "should create and yield a new section object" do
34
+ yielded = nil
35
+ @d.section :header do |section|
36
+ yielded = section
37
+ end
38
+ yielded.should be_a(FixedWidth::Section)
39
+ @d.sections.first.should == yielded
40
+ end
41
+
42
+ it "should magically build a section from an unknown method" do
43
+ FixedWidth::Section.should_receive(:new).with(:header, anything()).and_return(@section)
44
+ @d.header {}
45
+ end
46
+
47
+ it "should not create duplicate section names" do
48
+ lambda { @d.section(:header) {} }.should_not raise_error(FixedWidth::DuplicateSectionNameError)
49
+ lambda { @d.section(:header) {} }.should raise_error(FixedWidth::DuplicateSectionNameError, "Duplicate section name: 'header'")
50
+ end
51
+ end
52
+
53
+ describe "when creating a template" do
54
+ before(:each) do
55
+ @d = FixedWidth::Definition.new
56
+ @section = mock('section', :null_object => true)
57
+ end
58
+
59
+ it "should create a new section" do
60
+ FixedWidth::Section.should_receive(:new).with(:row, anything()).and_return(@section)
61
+ @d.template(:row) {}
62
+ end
63
+
64
+ it "should yield the new section" do
65
+ FixedWidth::Section.should_receive(:new).with(:row, anything()).and_return(@section)
66
+ yielded = nil
67
+ @d.template :row do |section|
68
+ yielded = section
69
+ end
70
+ yielded.should == @section
71
+ end
72
+
73
+ it "add a section to the templates collection" do
74
+ @d.should have(0).templates
75
+ @d.template :row do |t|
76
+ t.column :id, 3
77
+ end
78
+ @d.should have(1).templates
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,81 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe FixedWidth do
4
+
5
+ before(:each) do
6
+ @name = :doc
7
+ @options = { :align => :left }
8
+ end
9
+
10
+ describe "when defining a format" do
11
+ before(:each) do
12
+ @definition = mock('definition')
13
+ end
14
+
15
+ it "should create a new definition using the specified name and options" do
16
+ FixedWidth.should_receive(:define).with(@name, @options).and_return(@definition)
17
+ FixedWidth.define(@name , @options)
18
+ end
19
+
20
+ it "should pass the definition to the block" do
21
+ yielded = nil
22
+ FixedWidth.define(@name) do |y|
23
+ yielded = y
24
+ end
25
+ yielded.should be_a( FixedWidth::Definition )
26
+ end
27
+
28
+ it "should add to the internal definition count" do
29
+ FixedWidth.definitions.clear
30
+ FixedWidth.should have(0).definitions
31
+ FixedWidth.define(@name , @options) {}
32
+ FixedWidth.should have(1).definitions
33
+ end
34
+ end
35
+
36
+ describe "when creating file from data" do
37
+ it "should raise an error if the definition name is not found" do
38
+ lambda { FixedWidth.generate(:not_there, {}) }.should raise_error(ArgumentError)
39
+ end
40
+
41
+ it "should output a string" do
42
+ definition = mock('definition')
43
+ generator = mock('generator')
44
+ generator.should_receive(:generate).with({})
45
+ FixedWidth.should_receive(:definition).with(:test).and_return(definition)
46
+ FixedWidth::Generator.should_receive(:new).with(definition).and_return(generator)
47
+ FixedWidth.generate(:test, {})
48
+ end
49
+
50
+ it "should output a file" do
51
+ file = mock('file')
52
+ text = mock('string')
53
+ file.should_receive(:write).with(text)
54
+ FixedWidth.should_receive(:generate).with(:test, {}).and_return(text)
55
+ FixedWidth.write(file, :test, {})
56
+ end
57
+ end
58
+
59
+ describe "when parsing a file" do
60
+ before(:each) do
61
+ @file = mock('file')
62
+ end
63
+
64
+ it "should check the file exists" do
65
+ lambda { FixedWidth.parse(@file, :test, {}) }.should raise_error(ArgumentError)
66
+ end
67
+
68
+ it "should raise an error if the definition name is not found" do
69
+ FixedWidth.definitions.clear
70
+ lambda { FixedWidth.parse(@file, :test, {}) }.should raise_error(ArgumentError)
71
+ end
72
+
73
+ it "should create a parser and call parse" do
74
+ parser = mock("parser", :null_object => true)
75
+ definition = mock('definition')
76
+ FixedWidth.should_receive(:definition).with(:test).and_return(definition)
77
+ FixedWidth::Parser.should_receive(:new).with(definition, @file).and_return(parser)
78
+ FixedWidth.parse(@file, :test)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,48 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe FixedWidth::Generator do
4
+ before(:each) do
5
+ @definition = FixedWidth.define :test do |d|
6
+ d.header do |h|
7
+ h.trap { |line| line[0,4] == 'HEAD' }
8
+ h.column :type, 4
9
+ h.column :file_id, 10
10
+ end
11
+ d.body do |b|
12
+ b.trap { |line| line[0,4] =~ /[^(HEAD|FOOT)]/ }
13
+ b.column :first, 10
14
+ b.column :last, 10
15
+ end
16
+ d.footer do |f|
17
+ f.trap { |line| line[0,4] == 'FOOT' }
18
+ f.column :type, 4
19
+ f.column :file_id, 10
20
+ end
21
+ end
22
+ @data = {
23
+ :header => [ {:type => "HEAD", :file_id => "1" } ],
24
+ :body => [
25
+ {:first => "Paul", :last => "Hewson" },
26
+ {:first => "Dave", :last => "Evans" }
27
+ ],
28
+ :footer => [ {:type => "FOOT", :file_id => "1" }]
29
+ }
30
+ @generator = FixedWidth::Generator.new(@definition)
31
+ end
32
+
33
+ it "should raise an error if there is no data for a required section" do
34
+ @data.delete :header
35
+ lambda { @generator.generate(@data) }.should raise_error(FixedWidth::RequiredSectionEmptyError, "Required section 'header' was empty.")
36
+ end
37
+
38
+ it "should generate a string" do
39
+ expected = "HEAD 1\n Paul Hewson\n Dave Evans\nFOOT 1"
40
+ @generator.generate(@data).should == expected
41
+ end
42
+
43
+ it "should handle lazy data declaration (no array around single record for a section)" do
44
+ expected = "HEAD 1\n Paul Hewson\n Dave Evans\nFOOT 1"
45
+ @data[:header] = @data[:header].first
46
+ @generator.generate(@data).should == expected
47
+ end
48
+ end
@@ -0,0 +1,110 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe FixedWidth::Parser do
4
+ before(:each) do
5
+ @definition = mock('definition', :sections => [])
6
+ @file = mock("file")
7
+ @parser = FixedWidth::Parser.new(@definition, @file)
8
+ end
9
+
10
+ it "should read in a source file" do
11
+ @file.should_receive(:readlines).and_return(["\n"])
12
+ @parser.parse
13
+ end
14
+
15
+ describe "when parsing sections" do
16
+ before(:each) do
17
+ @definition = FixedWidth.define :test do |d|
18
+ d.header do |h|
19
+ h.trap { |line| line[0,4] == 'HEAD' }
20
+ h.column :type, 4
21
+ h.column :file_id, 10
22
+ end
23
+ d.body do |b|
24
+ b.trap { |line| line[0,4] =~ /[^(HEAD|FOOT)]/ }
25
+ b.column :first, 10
26
+ b.column :last, 10
27
+ end
28
+ d.footer do |f|
29
+ f.trap { |line| line[0,4] == 'FOOT' }
30
+ f.column :type, 4
31
+ f.column :file_id, 10
32
+ end
33
+ end
34
+ @parser = FixedWidth::Parser.new(@definition, @file)
35
+ end
36
+
37
+ it "should add lines to the proper sections" do
38
+ @file.should_receive(:readlines).and_return([
39
+ "HEAD 1\n",
40
+ " Paul Hewson\n",
41
+ " Dave Evans\n",
42
+ "FOOT 1\n"
43
+ ])
44
+ expected = {
45
+ :header => [ {:type => "HEAD", :file_id => "1" } ],
46
+ :body => [
47
+ {:first => "Paul", :last => "Hewson" },
48
+ {:first => "Dave", :last => "Evans" }
49
+ ],
50
+ :footer => [ {:type => "FOOT", :file_id => "1" } ]
51
+ }
52
+ result = @parser.parse
53
+ result.should == expected
54
+ end
55
+
56
+ it "should treat singular sections properly" do
57
+ @definition = FixedWidth.define :test do |d|
58
+ d.header(:singular => true) do |h|
59
+ h.trap { |line| line[0,4] == 'HEAD' }
60
+ h.column :type, 4
61
+ h.column :file_id, 10
62
+ end
63
+ d.body do |b|
64
+ b.trap { |line| line[0,4] =~ /[^(HEAD|FOOT)]/ }
65
+ b.column :first, 10
66
+ b.column :last, 10
67
+ end
68
+ d.footer(:singular => true) do |f|
69
+ f.trap { |line| line[0,4] == 'FOOT' }
70
+ f.column :type, 4
71
+ f.column :file_id, 10
72
+ end
73
+ end
74
+ @parser = FixedWidth::Parser.new(@definition, @file)
75
+ @file.should_receive(:readlines).and_return([
76
+ "HEAD 1\n",
77
+ " Paul Hewson\n",
78
+ " Dave Evans\n",
79
+ "FOOT 1\n"
80
+ ])
81
+ expected = {
82
+ :header => {:type => "HEAD", :file_id => "1" },
83
+ :body => [
84
+ {:first => "Paul", :last => "Hewson" },
85
+ {:first => "Dave", :last => "Evans" }
86
+ ],
87
+ :footer => {:type => "FOOT", :file_id => "1" }
88
+ }
89
+ result = @parser.parse
90
+ result.should == expected
91
+ end
92
+
93
+ it "should allow optional sections to be skipped" do
94
+ @definition.sections[0].optional = true
95
+ @definition.sections[2].optional = true
96
+ @file.should_receive(:readlines).and_return([
97
+ " Paul Hewson\n"
98
+ ])
99
+ expected = { :body => [ {:first => "Paul", :last => "Hewson" } ] }
100
+ @parser.parse.should == expected
101
+ end
102
+
103
+ it "should raise an error if a required section is not found" do
104
+ @file.should_receive(:readlines).and_return([
105
+ " Ryan Wood\n"
106
+ ])
107
+ lambda { @parser.parse }.should raise_error(FixedWidth::RequiredSectionNotFoundError, "Required section 'header' was not found.")
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,176 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe FixedWidth::Section do
4
+ before(:each) do
5
+ @section = FixedWidth::Section.new(:body)
6
+ end
7
+
8
+ it "should have no columns after creation" do
9
+ @section.columns.should be_empty
10
+ end
11
+
12
+ describe "when adding columns" do
13
+ it "should build an ordered column list" do
14
+ @section.should have(0).columns
15
+
16
+ col1 = @section.column :id, 10
17
+ col2 = @section.column :name, 30
18
+ col3 = @section.column :state, 2
19
+
20
+ @section.should have(3).columns
21
+ @section.columns[0].should be(col1)
22
+ @section.columns[1].should be(col2)
23
+ @section.columns[2].should be(col3)
24
+ end
25
+
26
+ it "should create spacer columns" do
27
+ @section.should have(0).columns
28
+ @section.spacer(5)
29
+ @section.should have(1).columns
30
+ end
31
+
32
+ it "can should override the alignment of the definition" do
33
+ section = FixedWidth::Section.new('name', :align => :left)
34
+ section.options[:align].should == :left
35
+ end
36
+
37
+ it "should use a missing method to create a column" do
38
+ @section.should have(0).columns
39
+ @section.first_name 5
40
+ @section.should have(1).columns
41
+ end
42
+
43
+ it "should prevent duplicate column names without any groupings" do
44
+ @section.column :id, 10
45
+ lambda { @section.column(:id, 30) }.should raise_error(FixedWidth::DuplicateColumnNameError, /column named 'id'/)
46
+ end
47
+
48
+ it "should prevent column names that already exist as groups" do
49
+ @section.column :foo, 11, :group => :id
50
+ lambda { @section.column(:id, 30) }.should raise_error(FixedWidth::DuplicateGroupNameError, /group named 'id'/)
51
+ end
52
+
53
+ it "should prevent group names that already exist as columns" do
54
+ @section.column :foo, 11
55
+ lambda { @section.column(:id, 30, :group => :foo) }.should raise_error(FixedWidth::DuplicateGroupNameError, /column named 'foo'/)
56
+ end
57
+
58
+ it "should prevent duplicate column names within groups" do
59
+ @section.column :id, 10, :group => :foo
60
+ lambda { @section.column(:id, 30, :group => :foo) }.should raise_error(FixedWidth::DuplicateColumnNameError, /column named 'id' in the ':foo' group/)
61
+ end
62
+
63
+ it "should allow duplicate column names in different groups" do
64
+ @section.column :id, 10, :group => :foo
65
+ lambda { @section.column(:id, 30, :group => :bar) }.should_not raise_error(FixedWidth::DuplicateColumnNameError)
66
+ end
67
+
68
+ it "should allow duplicate column names that are reserved (i.e. spacer)" do
69
+ @section.spacer 10
70
+ lambda { @section.spacer 10 }.should_not raise_error(FixedWidth::DuplicateColumnNameError)
71
+ end
72
+ end
73
+
74
+ it "should accept and store the trap as a block" do
75
+ @section.trap { |v| v == 4 }
76
+ trap = @section.instance_variable_get(:@trap)
77
+ trap.should be_a(Proc)
78
+ trap.call(4).should == true
79
+ end
80
+
81
+ describe "when adding a template" do
82
+ before(:each) do
83
+ @template = mock('templated section', :columns => [1,2,3], :options => {})
84
+ @definition = mock("definition", :templates => { :test => @template } )
85
+ @section.definition = @definition
86
+ end
87
+
88
+ it "should ensure the template exists" do
89
+ @definition.stub! :templates => {}
90
+ lambda { @section.template(:none) }.should raise_error(ArgumentError)
91
+ end
92
+
93
+ it "should add the template columns to the current column list" do
94
+ @section.template :test
95
+ @section.should have(3).columns
96
+ end
97
+
98
+ it "should merge the template option" do
99
+ @section = FixedWidth::Section.new(:body, :align => :left)
100
+ @section.definition = @definition
101
+ @template.stub! :options => {:align => :right}
102
+ @section.template :test
103
+ @section.options.should == {:align => :left}
104
+ end
105
+ end
106
+
107
+ describe "when formatting a row" do
108
+ before(:each) do
109
+ @data = { :id => 3, :name => "Ryan" }
110
+ end
111
+
112
+ it "should default to string data aligned right" do
113
+ @section.column(:id, 5)
114
+ @section.column(:name, 10)
115
+ @section.format( @data ).should == " 3 Ryan"
116
+ end
117
+
118
+ it "should left align if asked" do
119
+ @section.column(:id, 5)
120
+ @section.column(:name, 10, :align => :left)
121
+ @section.format(@data).should == " 3Ryan "
122
+ end
123
+
124
+ it "should read from groups" do
125
+ @data = { :id => 3, :foo => { :name => "Ryan" } }
126
+ @section.column(:id, 5)
127
+ @section.column(:name, 10, :align => :left, :group => :foo)
128
+ @section.format(@data).should == " 3Ryan "
129
+ end
130
+ end
131
+
132
+ describe "when parsing a file" do
133
+ before(:each) do
134
+ @line = ' 45 Ryan WoodSC '
135
+ @section = FixedWidth::Section.new(:body)
136
+ @column_content = { :id => 5, :first => 10, :last => 10, :state => 2 }
137
+ end
138
+
139
+ it "should return a key for key column" do
140
+ @column_content.each { |k,v| @section.column(k, v) }
141
+ parsed = @section.parse(@line)
142
+ @column_content.each_key { |name| parsed.should have_key(name) }
143
+ end
144
+
145
+ it "should not return a key for reserved names" do
146
+ @column_content.each { |k,v| @section.column(k, v) }
147
+ @section.spacer 5
148
+ @section.should have(5).columns
149
+ parsed = @section.parse(@line)
150
+ parsed.should have(4).keys
151
+ end
152
+
153
+ it "should break columns into groups" do
154
+ @section.column(:id, 5)
155
+ @section.column(:first, 10, :group => :name)
156
+ @section.column(:last, 10, :group => :name)
157
+ @section.column(:state, 2, :group => :address)
158
+ @section.spacer 5
159
+ @section.should have(5).columns
160
+ parsed = @section.parse(@line)
161
+ parsed.should have(3).keys
162
+ parsed[:id].should == '45'
163
+ parsed[:name][:first].should == 'Ryan'
164
+ parsed[:name][:last].should == 'Wood'
165
+ parsed[:address][:state].should == 'SC'
166
+ end
167
+ end
168
+
169
+ it "should try to match a line using the trap" do
170
+ @section.trap do |line|
171
+ line == 'hello'
172
+ end
173
+ @section.match('hello').should be_true
174
+ @section.match('goodbye').should be_false
175
+ end
176
+ end