gotime-slither 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,109 @@
1
+ class Slither
2
+ class Section
3
+ attr_accessor :definition, :optional
4
+ attr_reader :name, :columns, :options, :length
5
+
6
+ RESERVED_NAMES = [:spacer]
7
+
8
+ def initialize(name, options = {})
9
+ @name = name
10
+ @options = options
11
+ @columns = []
12
+ @trap = options[:trap]
13
+ @optional = options[:optional] || false
14
+ @length = 0
15
+ end
16
+
17
+ def column(name, length, options = {})
18
+ raise(Slither::DuplicateColumnNameError, "You have already defined a column named '#{name}'.") if @columns.map do |c|
19
+ RESERVED_NAMES.include?(c.name) ? nil : c.name
20
+ end.flatten.include?(name)
21
+ col = Column.new(name, length, @options.merge(options))
22
+ @columns << col
23
+ @length += length
24
+ col
25
+ end
26
+
27
+ def spacer(length)
28
+ column(:spacer, length)
29
+ end
30
+
31
+ def trap(&block)
32
+ @trap = block
33
+ end
34
+
35
+ def template(name)
36
+ template = @definition.templates[name]
37
+ raise ArgumentError, "Template #{name} not found as a known template." unless template
38
+ @columns += template.columns
39
+ @length += template.length
40
+ # Section options should trump template options
41
+ @options = template.options.merge(@options)
42
+ end
43
+
44
+ def format(data)
45
+ # raise( ColumnMismatchError,
46
+ # "The '#{@name}' section has #{@columns.size} column(s) defined, but there are #{data.size} column(s) provided in the data."
47
+ # ) unless @columns.size == data.size
48
+ row = ''
49
+ @columns.each do |column|
50
+ row += column.format(data[column.name])
51
+ end
52
+ row
53
+ end
54
+
55
+ def parse(line)
56
+ line_data = divide( line, @columns.map(&:length) )
57
+ row = {}
58
+ i = 0
59
+ @columns.each do |c|
60
+ unless RESERVED_NAMES.include?(c.name)
61
+ row[c.name] = (c.parse_length == 1 ?
62
+ c.parse(line_data[i]) : c.parse(line_data[i, c.parse_length]))
63
+ end
64
+ i += c.parse_length
65
+ end
66
+ row
67
+ end
68
+
69
+ def parse_when_problem(line)
70
+ line_data = divide( line, @columns.map(&:length) )
71
+ row = ''
72
+ @columns.each_with_index do |c, i|
73
+ row << "\n'#{c.name}':'#{line_data[i]}'" unless RESERVED_NAMES.include?(c.name)
74
+ end
75
+ row
76
+ end
77
+
78
+ def match(raw_line)
79
+ raw_line.nil? ? false : @trap.call(raw_line)
80
+ end
81
+
82
+ def method_missing(method, *args)
83
+ column(method, *args)
84
+ end
85
+
86
+ private
87
+
88
+ def unpacker
89
+ @columns.map { |c| c.unpacker }.join('')
90
+ end
91
+
92
+ def divide(string, sections)
93
+ result = []
94
+ str = string.dup
95
+ unless @definition.options[:force_character_offset]
96
+ result = str.unpack(unpacker)
97
+ result.each do |s|
98
+ s.force_encoding(string.encoding) if s.respond_to? :force_encoding
99
+ end
100
+ else
101
+ sections.each do |s|
102
+ result << str.slice!(0..(s-1))
103
+ end
104
+ end
105
+ result
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,57 @@
1
+ class Slither
2
+
3
+ VERSION = '0.99.5'
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
+ class LineWrongSizeError < StandardError; end
11
+ class SectionsNotSameLengthError < StandardError; end
12
+
13
+
14
+ def self.define(name, options = {}, &block)
15
+ definition = Definition.new(options)
16
+ yield(definition)
17
+ definitions[name] = definition
18
+ definition
19
+ end
20
+
21
+ def self.generate(definition_name, data)
22
+ definition = definition(definition_name)
23
+ raise ArgumentError, "Definition name '#{name}' was not found." unless definition
24
+ generator = Generator.new(definition)
25
+ generator.generate(data)
26
+ end
27
+
28
+ def self.write(filename, definition_name, data)
29
+ File.open(filename, 'w') do |f|
30
+ f.write generate(definition_name, data)
31
+ end
32
+ end
33
+
34
+ def self.parse(filename, definition_name)
35
+ raise ArgumentError, "File #{filename} does not exist." unless File.exists?(filename)
36
+
37
+ file_io = File.open(filename, 'r')
38
+ parseIo(file_io, definition_name)
39
+ end
40
+
41
+ def self.parseIo(io, definition_name)
42
+ definition = definition(definition_name)
43
+ raise ArgumentError, "Definition name '#{definition_name}' was not found." unless definition
44
+ parser = Parser.new(definition, io)
45
+ definition.options[:by_bytes] ? parser.parse_by_bytes : parser.parse(definition.options[:error_handler])
46
+ end
47
+
48
+ private
49
+
50
+ def self.definitions
51
+ @@definitions ||= {}
52
+ end
53
+
54
+ def self.definition(name)
55
+ definitions[name]
56
+ end
57
+ end
@@ -0,0 +1,229 @@
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 accept a binary byte type" do
82
+ @column = Slither::Column.new(:dat, 1, :type=> :binary)
83
+ @column.parse(0x18).should == 0x18
84
+ end
85
+
86
+ it "should support the float type" do
87
+ @column = Slither::Column.new(:amount, 10, :type=> :float)
88
+ @column.parse(' 234.45').should == 234.45
89
+ @column.parse('234.5600').should == 234.56
90
+ @column.parse(' 234').should == 234.0
91
+ @column.parse('00000234').should == 234.0
92
+ @column.parse('Ryan ').should == 0
93
+ @column.parse('00023.45').should == 23.45
94
+ end
95
+
96
+ it "should support the money_with_implied_decimal type" do
97
+ @column = Slither::Column.new(:amount, 10, :type=> :money_with_implied_decimal)
98
+ @column.parse(' 23445').should == 234.45
99
+ end
100
+
101
+ it "should support the date type" do
102
+ @column = Slither::Column.new(:date, 10, :type => :date)
103
+ dt = @column.parse('2009-08-22')
104
+ dt.should be_a(Date)
105
+ dt.to_s.should == '2009-08-22'
106
+ end
107
+
108
+ it "should use the format option with date type if available" do
109
+ @column = Slither::Column.new(:date, 10, :type => :date, :format => "%m%d%Y")
110
+ dt = @column.parse('08222009')
111
+ dt.should be_a(Date)
112
+ dt.to_s.should == '2009-08-22'
113
+ end
114
+ end
115
+
116
+ describe "when applying formatting options" do
117
+ it "should return a proper formatter" do
118
+ @column = Slither::Column.new(@name, @length, :align => :left)
119
+ @column.send(:formatter).should == "%-5s"
120
+ end
121
+
122
+ it "should respect a right alignment" do
123
+ @column = Slither::Column.new(@name, @length, :align => :right)
124
+ @column.format(25).should == ' 25'
125
+ end
126
+
127
+ it "should respect a left alignment" do
128
+ @column = Slither::Column.new(@name, @length, :align => :left)
129
+ @column.format(25).should == '25 '
130
+ end
131
+
132
+ it "should respect padding with spaces" do
133
+ @column = Slither::Column.new(@name, @length, :padding => :space)
134
+ @column.format(25).should == ' 25'
135
+ end
136
+
137
+ it "should respect padding with zeros with integer types" do
138
+ @column = Slither::Column.new(@name, @length, :type => :integer, :padding => :zero)
139
+ @column.format(25).should == '00025'
140
+ end
141
+
142
+ describe "that is a float type" do
143
+ it "should respect padding with zeros aligned right" do
144
+ @column = Slither::Column.new(@name, @length, :type => :float, :padding => :zero, :align => :right)
145
+ @column.format(4.45).should == '04.45'
146
+ end
147
+
148
+ it "should respect padding with zeros aligned left" do
149
+ @column = Slither::Column.new(@name, @length, :type => :float, :padding => :zero, :align => :left)
150
+ @column.format(4.45).should == '4.450'
151
+ end
152
+ end
153
+ end
154
+
155
+ describe "when formatting values for a file" do
156
+ it "should default to a string" do
157
+ @column = Slither::Column.new(:name, 10)
158
+ @column.format('Bill').should == ' Bill'
159
+ end
160
+
161
+ describe "whose size is too long" do
162
+ it "should raise an error if truncate is false" do
163
+ @value = "XX" * @length
164
+ lambda { @column.format(@value) }.should raise_error(
165
+ Slither::FormattedStringExceedsLengthError,
166
+ "The formatted value '#{@value}' in column '#{@name}' exceeds the allowed length of #{@length} chararacters."
167
+ )
168
+ end
169
+
170
+ it "should truncate from the left if truncate is true and aligned left" do
171
+ @column = Slither::Column.new(@name, @length, :truncate => true, :align => :left)
172
+ @column.format("This is too long").should == "This "
173
+ end
174
+
175
+ it "should truncate from the right if truncate is true and aligned right" do
176
+ @column = Slither::Column.new(@name, @length, :truncate => true, :align => :right)
177
+ @column.format("This is too long").should == " long"
178
+ end
179
+ end
180
+
181
+ it "should support the integer type" do
182
+ @column = Slither::Column.new(:amount, 10, :type => :integer)
183
+ @column.format(234).should == ' 234'
184
+ @column.format('234').should == ' 234'
185
+ end
186
+
187
+ it "should support the float type" do
188
+ @column = Slither::Column.new(:amount, 10, :type => :float)
189
+ @column.format(234.45).should == ' 234.45'
190
+ @column.format('234.4500').should == ' 234.45'
191
+ @column.format('3').should == ' 3.0'
192
+ end
193
+
194
+ it "should support the float type with a format" do
195
+ @column = Slither::Column.new(:amount, 10, :type => :float, :format => "%.3f")
196
+ @column.format(234.45).should == ' 234.450'
197
+ @column.format('234.4500').should == ' 234.450'
198
+ @column.format('3').should == ' 3.000'
199
+ end
200
+
201
+ it "should support the float type with a format, alignment and padding" do
202
+ @column = Slither::Column.new(:amount, 10, :type => :float, :format => "%.2f", :align => :left, :padding => :zero)
203
+ @column.format(234.45).should == '234.450000'
204
+ @column = Slither::Column.new(:amount, 10, :type => :float, :format => "%.2f", :align => :right, :padding => :zero)
205
+ @column.format('234.400').should == '0000234.40'
206
+ @column = Slither::Column.new(:amount, 10, :type => :float, :format => "%.4f", :align => :left, :padding => :space)
207
+ @column.format('3').should == '3.0000 '
208
+ end
209
+
210
+ it "should support the money_with_implied_decimal type" do
211
+ @column = Slither::Column.new(:amount, 10, :type=> :money_with_implied_decimal)
212
+ @column.format(234.450).should == " 23445"
213
+ @column.format(12.34).should == " 1234"
214
+ end
215
+
216
+ it "should support the date type" do
217
+ dt = Date.new(2009, 8, 22)
218
+ @column = Slither::Column.new(:date, 10, :type => :date)
219
+ @column.format(dt).should == '2009-08-22'
220
+ end
221
+
222
+ it "should support the date type with a :format" do
223
+ dt = Date.new(2009, 8, 22)
224
+ @column = Slither::Column.new(:date, 8, :type => :date, :format => "%m%d%Y")
225
+ @column.format(dt).should == '08222009'
226
+ end
227
+ end
228
+
229
+ end
@@ -0,0 +1,86 @@
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
+ d = Slither::Definition.new
20
+ d.options[:align].should == :right
21
+ d.section('name', :align => :left) {}
22
+ section = nil
23
+ d.sections.each { |sec| section = sec if sec.name == 'name' }
24
+ section.options[:align].should eq(:left)
25
+ end
26
+ end
27
+
28
+ describe "when creating a section" do
29
+ before(:each) do
30
+ @d = Slither::Definition.new
31
+ @section = mock('section').as_null_object
32
+ end
33
+
34
+ it "should create and yield a new section object" do
35
+ yielded = nil
36
+ @d.section :header do |section|
37
+ yielded = section
38
+ end
39
+ yielded.should be_a(Slither::Section)
40
+ @d.sections.first.should == yielded
41
+ end
42
+
43
+ it "should magically build a section from an unknown method" do
44
+ Slither::Section.should_receive(:new).with(:header, anything()).and_return(@section)
45
+ @d.header {}
46
+ end
47
+
48
+ it "should not create duplicate section names" do
49
+ lambda { @d.section(:header) {} }.should_not raise_error(ArgumentError)
50
+ lambda { @d.section(:header) {} }.should raise_error(ArgumentError, "Reserved or duplicate section name: 'header'")
51
+ end
52
+
53
+ it "should throw an error if a reserved section name is used" do
54
+ lambda { @d.section(:spacer) {} }.should raise_error(ArgumentError, "Reserved or duplicate section name: 'spacer'")
55
+ end
56
+ end
57
+
58
+ describe "when creating a template" do
59
+ before(:each) do
60
+ @d = Slither::Definition.new
61
+ @section = mock('section').as_null_object
62
+ end
63
+
64
+ it "should create a new section" do
65
+ Slither::Section.should_receive(:new).with(:row, anything()).and_return(@section)
66
+ @d.template(:row) {}
67
+ end
68
+
69
+ it "should yield the new section" do
70
+ Slither::Section.should_receive(:new).with(:row, anything()).and_return(@section)
71
+ yielded = nil
72
+ @d.template :row do |section|
73
+ yielded = section
74
+ end
75
+ yielded.should == @section
76
+ end
77
+
78
+ it "add a section to the templates collection" do
79
+ @d.should have(0).templates
80
+ @d.template :row do |t|
81
+ t.column :id, 3
82
+ end
83
+ @d.should have(1).templates
84
+ end
85
+ end
86
+ end