gotime-slither 1.0.2

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,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