slither 0.99.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ class Slither
2
+
3
+ VERSION = '0.99.0'
4
+
5
+ class DuplicateColumnNameError < StandardError; end
6
+ class RequiredSectionNotFoundError < StandardError; end
7
+ class RequiredSectionEmptyError < StandardError; end
8
+ class FormattedStringExceedsLengthError < StandardError; end
9
+ class ColumnMismatchError < StandardError; end
10
+
11
+
12
+ def self.define(name, options = {}, &block)
13
+ definition = Definition.new(options)
14
+ yield(definition)
15
+ definitions[name] = definition
16
+ definition
17
+ end
18
+
19
+ def self.generate(definition_name, data)
20
+ definition = definition(definition_name)
21
+ raise ArgumentError, "Definition name '#{name}' was not found." unless definition
22
+ generator = Generator.new(definition)
23
+ generator.generate(data)
24
+ end
25
+
26
+ def self.write(filename, definition_name, data)
27
+ File.open(filename, 'w') do |f|
28
+ f.write generate(definition_name, data)
29
+ end
30
+ end
31
+
32
+ def self.parse(filename, definition_name)
33
+ raise ArgumentError, "File #{filename} does not exist." unless File.exists?(filename)
34
+ definition = definition(definition_name)
35
+ raise ArgumentError, "Definition name '#{definition_name}' was not found." unless definition
36
+ parser = Parser.new(definition, filename)
37
+ parser.parse
38
+ end
39
+
40
+ private
41
+
42
+ def self.definitions
43
+ @@definitions ||= {}
44
+ end
45
+
46
+ def self.definition(name)
47
+ definitions[name]
48
+ end
49
+ end
Binary file
@@ -0,0 +1,224 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Slither::Column do
4
+ before(:each) do
5
+ @name = :id
6
+ @length = 5
7
+ @column = Slither::Column.new(@name, @length)
8
+ end
9
+
10
+ describe "when being created" do
11
+ it "should have a name" do
12
+ @column.name.should == @name
13
+ end
14
+
15
+ it "should have a length" do
16
+ @column.length.should == @length
17
+ end
18
+
19
+ it "should have a default padding" do
20
+ @column.padding.should == :space
21
+ end
22
+
23
+ it "should have a default alignment" do
24
+ @column.alignment.should == :right
25
+ end
26
+
27
+ it "should return a proper formatter" do
28
+ @column.send(:formatter).should == "%5s"
29
+ end
30
+ end
31
+
32
+ describe "when specifying an alignment" do
33
+ before(:each) do
34
+ @column = Slither::Column.new(@name, @length, :align => :left)
35
+ end
36
+
37
+ it "should only accept :right or :left for an alignment" do
38
+ lambda{ Slither::Column.new(@name, @length, :align => :bogus) }.should raise_error(ArgumentError, "Option :align only accepts :right (default) or :left")
39
+ end
40
+
41
+ it "should override the default alignment" do
42
+ @column.alignment.should == :left
43
+ end
44
+ end
45
+
46
+ describe "when specifying padding" do
47
+ before(:each) do
48
+ @column = Slither::Column.new(@name, @length, :padding => :zero)
49
+ end
50
+
51
+ it "should accept only :space or :zero" do
52
+ lambda{ Slither::Column.new(@name, @length, :padding => :bogus) }.should raise_error(ArgumentError, "Option :padding only accepts :space (default) or :zero")
53
+ end
54
+
55
+ it "should override the default padding" do
56
+ @column.padding.should == :zero
57
+ end
58
+ end
59
+
60
+ it "should return the proper unpack value for a string" do
61
+ @column.send(:unpacker).should == 'A5'
62
+ end
63
+
64
+ describe "when parsing a value from a file" do
65
+ it "should default to a string" do
66
+ @column.parse(' name ').should == 'name'
67
+ @column.parse(' 234').should == '234'
68
+ @column.parse('000000234').should == '000000234'
69
+ @column.parse('12.34').should == '12.34'
70
+ end
71
+
72
+ it "should support the integer type" do
73
+ @column = Slither::Column.new(:amount, 10, :type=> :integer)
74
+ @column.parse('234 ').should == 234
75
+ @column.parse(' 234').should == 234
76
+ @column.parse('00000234').should == 234
77
+ @column.parse('Ryan ').should == 0
78
+ @column.parse('00023.45').should == 23
79
+ end
80
+
81
+ it "should support the float type" do
82
+ @column = Slither::Column.new(:amount, 10, :type=> :float)
83
+ @column.parse(' 234.45').should == 234.45
84
+ @column.parse('234.5600').should == 234.56
85
+ @column.parse(' 234').should == 234.0
86
+ @column.parse('00000234').should == 234.0
87
+ @column.parse('Ryan ').should == 0
88
+ @column.parse('00023.45').should == 23.45
89
+ end
90
+
91
+ it "should support the money_with_implied_decimal type" do
92
+ @column = Slither::Column.new(:amount, 10, :type=> :money_with_implied_decimal)
93
+ @column.parse(' 23445').should == 234.45
94
+ end
95
+
96
+ it "should support the date type" do
97
+ @column = Slither::Column.new(:date, 10, :type => :date)
98
+ dt = @column.parse('2009-08-22')
99
+ dt.should be_a(Date)
100
+ dt.to_s.should == '2009-08-22'
101
+ end
102
+
103
+ it "should use the format option with date type if available" do
104
+ @column = Slither::Column.new(:date, 10, :type => :date, :format => "%m%d%Y")
105
+ dt = @column.parse('08222009')
106
+ dt.should be_a(Date)
107
+ dt.to_s.should == '2009-08-22'
108
+ end
109
+ end
110
+
111
+ describe "when applying formatting options" do
112
+ it "should return a proper formatter" do
113
+ @column = Slither::Column.new(@name, @length, :align => :left)
114
+ @column.send(:formatter).should == "%-5s"
115
+ end
116
+
117
+ it "should respect a right alignment" do
118
+ @column = Slither::Column.new(@name, @length, :align => :right)
119
+ @column.format(25).should == ' 25'
120
+ end
121
+
122
+ it "should respect a left alignment" do
123
+ @column = Slither::Column.new(@name, @length, :align => :left)
124
+ @column.format(25).should == '25 '
125
+ end
126
+
127
+ it "should respect padding with spaces" do
128
+ @column = Slither::Column.new(@name, @length, :padding => :space)
129
+ @column.format(25).should == ' 25'
130
+ end
131
+
132
+ it "should respect padding with zeros with integer types" do
133
+ @column = Slither::Column.new(@name, @length, :type => :integer, :padding => :zero)
134
+ @column.format(25).should == '00025'
135
+ end
136
+
137
+ describe "that is a float type" do
138
+ it "should respect padding with zeros aligned right" do
139
+ @column = Slither::Column.new(@name, @length, :type => :float, :padding => :zero, :align => :right)
140
+ @column.format(4.45).should == '04.45'
141
+ end
142
+
143
+ it "should respect padding with zeros aligned left" do
144
+ @column = Slither::Column.new(@name, @length, :type => :float, :padding => :zero, :align => :left)
145
+ @column.format(4.45).should == '4.450'
146
+ end
147
+ end
148
+ end
149
+
150
+ describe "when formatting values for a file" do
151
+ it "should default to a string" do
152
+ @column = Slither::Column.new(:name, 10)
153
+ @column.format('Bill').should == ' Bill'
154
+ end
155
+
156
+ describe "whose size is too long" do
157
+ it "should raise an error if truncate is false" do
158
+ @value = "XX" * @length
159
+ lambda { @column.format(@value) }.should raise_error(
160
+ Slither::FormattedStringExceedsLengthError,
161
+ "The formatted value '#{@value}' in column '#{@name}' exceeds the allowed length of #{@length} chararacters."
162
+ )
163
+ end
164
+
165
+ it "should truncate from the left if truncate is true and aligned left" do
166
+ @column = Slither::Column.new(@name, @length, :truncate => true, :align => :left)
167
+ @column.format("This is too long").should == "This "
168
+ end
169
+
170
+ it "should truncate from the right if truncate is true and aligned right" do
171
+ @column = Slither::Column.new(@name, @length, :truncate => true, :align => :right)
172
+ @column.format("This is too long").should == " long"
173
+ end
174
+ end
175
+
176
+ it "should support the integer type" do
177
+ @column = Slither::Column.new(:amount, 10, :type => :integer)
178
+ @column.format(234).should == ' 234'
179
+ @column.format('234').should == ' 234'
180
+ end
181
+
182
+ it "should support the float type" do
183
+ @column = Slither::Column.new(:amount, 10, :type => :float)
184
+ @column.format(234.45).should == ' 234.45'
185
+ @column.format('234.4500').should == ' 234.45'
186
+ @column.format('3').should == ' 3.0'
187
+ end
188
+
189
+ it "should support the float type with a format" do
190
+ @column = Slither::Column.new(:amount, 10, :type => :float, :format => "%.3f")
191
+ @column.format(234.45).should == ' 234.450'
192
+ @column.format('234.4500').should == ' 234.450'
193
+ @column.format('3').should == ' 3.000'
194
+ end
195
+
196
+ it "should support the float type with a format, alignment and padding" do
197
+ @column = Slither::Column.new(:amount, 10, :type => :float, :format => "%.2f", :align => :left, :padding => :zero)
198
+ @column.format(234.45).should == '234.450000'
199
+ @column = Slither::Column.new(:amount, 10, :type => :float, :format => "%.2f", :align => :right, :padding => :zero)
200
+ @column.format('234.400').should == '0000234.40'
201
+ @column = Slither::Column.new(:amount, 10, :type => :float, :format => "%.4f", :align => :left, :padding => :space)
202
+ @column.format('3').should == '3.0000 '
203
+ end
204
+
205
+ it "should support the money_with_implied_decimal type" do
206
+ @column = Slither::Column.new(:amount, 10, :type=> :money_with_implied_decimal)
207
+ @column.format(234.450).should == " 23445"
208
+ @column.format(12.34).should == " 1234"
209
+ end
210
+
211
+ it "should support the date type" do
212
+ dt = Date.new(2009, 8, 22)
213
+ @column = Slither::Column.new(:date, 10, :type => :date)
214
+ @column.format(dt).should == '2009-08-22'
215
+ end
216
+
217
+ it "should support the date type with a :format" do
218
+ dt = Date.new(2009, 8, 22)
219
+ @column = Slither::Column.new(:date, 8, :type => :date, :format => "%m%d%Y")
220
+ @column.format(dt).should == '08222009'
221
+ end
222
+ end
223
+
224
+ end
@@ -0,0 +1,85 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Slither::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 = Slither::Definition.new :align => :right
10
+ d.options[:align].should == :right
11
+ end
12
+
13
+ it "should default to being right aligned" do
14
+ d = Slither::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
+ Slither::Section.should_receive(:new).with('name', {:align => :left}).and_return(section)
21
+ d = Slither::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 = Slither::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(Slither::Section)
39
+ @d.sections.first.should == yielded
40
+ end
41
+
42
+ it "should magically build a section from an unknown method" do
43
+ Slither::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(ArgumentError)
49
+ lambda { @d.section(:header) {} }.should raise_error(ArgumentError, "Reserved or duplicate section name: 'header'")
50
+ end
51
+
52
+ it "should throw an error if a reserved section name is used" do
53
+ lambda { @d.section(:spacer) {} }.should raise_error(ArgumentError, "Reserved or duplicate section name: 'spacer'")
54
+ end
55
+ end
56
+
57
+ describe "when creating a template" do
58
+ before(:each) do
59
+ @d = Slither::Definition.new
60
+ @section = mock('section', :null_object => true)
61
+ end
62
+
63
+ it "should create a new section" do
64
+ Slither::Section.should_receive(:new).with(:row, anything()).and_return(@section)
65
+ @d.template(:row) {}
66
+ end
67
+
68
+ it "should yield the new section" do
69
+ Slither::Section.should_receive(:new).with(:row, anything()).and_return(@section)
70
+ yielded = nil
71
+ @d.template :row do |section|
72
+ yielded = section
73
+ end
74
+ yielded.should == @section
75
+ end
76
+
77
+ it "add a section to the templates collection" do
78
+ @d.should have(0).templates
79
+ @d.template :row do |t|
80
+ t.column :id, 3
81
+ end
82
+ @d.should have(1).templates
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,42 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Slither::Generator do
4
+ before(:each) do
5
+ @definition = Slither.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 = Slither::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(Slither::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
+ end
@@ -0,0 +1,74 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Slither::Parser do
4
+ before(:each) do
5
+ @definition = mock('definition', :sections => [])
6
+ @file = mock("file", :gets => nil)
7
+ @file_name = 'test.txt'
8
+ @parser = Slither::Parser.new(@definition, @file_name)
9
+ end
10
+
11
+ it "should open and yield the source file" do
12
+ File.should_receive(:open).with(@file_name, 'r').and_yield(@file)
13
+ @parser.parse
14
+ end
15
+
16
+ describe "when parsing sections" do
17
+ before(:each) do
18
+ @definition = Slither.define :test do |d|
19
+ d.header do |h|
20
+ h.trap { |line| line[0,4] == 'HEAD' }
21
+ h.column :type, 4
22
+ h.column :file_id, 10
23
+ end
24
+ d.body do |b|
25
+ b.trap { |line| line[0,4] =~ /[^(HEAD|FOOT)]/ }
26
+ b.column :first, 10
27
+ b.column :last, 10
28
+ end
29
+ d.footer do |f|
30
+ f.trap { |line| line[0,4] == 'FOOT' }
31
+ f.column :type, 4
32
+ f.column :file_id, 10
33
+ end
34
+ end
35
+ File.should_receive(:open).with(@file_name, 'r').and_yield(@file)
36
+ @parser = Slither::Parser.new(@definition, @file_name)
37
+ end
38
+
39
+ it "should add lines to the proper sections" do
40
+ @file.should_receive(:gets).exactly(4).times.and_return(
41
+ 'HEAD 1',
42
+ ' Paul Hewson',
43
+ ' Dave Evans',
44
+ 'FOOT 1',
45
+ nil
46
+ )
47
+ expected = {
48
+ :header => [ {:type => "HEAD", :file_id => "1" }],
49
+ :body => [
50
+ {:first => "Paul", :last => "Hewson" },
51
+ {:first => "Dave", :last => "Evans" }
52
+ ],
53
+ :footer => [ {:type => "FOOT", :file_id => "1" }]
54
+ }
55
+ result = @parser.parse
56
+ result.should == expected
57
+ end
58
+
59
+ it "should allow optional sections to be skipped" do
60
+ @definition.sections[0].optional = true
61
+ @definition.sections[2].optional = true
62
+ @file.should_receive(:gets).twice.and_return(' Paul Hewson', nil)
63
+ expected = { :body => [ {:first => "Paul", :last => "Hewson" } ] }
64
+ @parser.parse.should == expected
65
+ end
66
+
67
+ it "should raise an error if a required section is not found" do
68
+ @file.should_receive(:gets).twice.and_return(' Ryan Wood', nil)
69
+ lambda { @parser.parse }.should raise_error(Slither::RequiredSectionNotFoundError, "Required section 'header' was not found.")
70
+ end
71
+
72
+ # it "raise an error if a section limit is over run"
73
+ end
74
+ end