ryanwood-slither 0.99.0 → 0.99.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,4 +1,10 @@
1
1
  == 0.99.0 / 2009-04-14
2
2
 
3
3
  * Initial Release
4
- * Happy Birthday!
4
+
5
+ == 0.99.1 / 2009-04-22
6
+
7
+ * Make the missing method build a column i.e. body.record_type 1
8
+ * Prevent duplicate column names
9
+ * Better error messages
10
+ * Implement custom padding (spaces (default), zero)
data/README.rdoc CHANGED
@@ -24,31 +24,23 @@ A simple, clean DSL for describing, writing, and parsing fixed-width text files.
24
24
  end
25
25
 
26
26
  # Create a header section
27
- d.header, :align => :left do |header|
28
-
27
+ d.header, :align => :left do |header|
29
28
  # The trap tells Slither which lines should fall into this section
30
- header.trap do |line|
31
- line[0,4] == 'HEAD'
32
- end
33
-
29
+ header.trap { |line| line[0,4] == 'HEAD' }
34
30
  # Use the boundary template for the columns
35
31
  header.template :boundary
36
32
  end
37
33
 
38
34
  d.body do |body|
39
- body.trap do |line|
40
- line[0,4] =~ /[^(HEAD|FOOT)]/
41
- end
35
+ body.trap { |line| line[0,4] =~ /[^(HEAD|FOOT)]/ }
42
36
  body.column :id, 10, :type => :integer
43
37
  body.column :name, 10, :align => :left
44
38
  body.spacer 3
45
39
  body.column :state, 2
46
40
  end
47
41
 
48
- d.footer, :, :limit => 1 do |footer|
49
- footer.trap do |line|
50
- line[0,4] == 'FOOT'
51
- end
42
+ d.footer do |footer|
43
+ footer.trap { |line| line[0,4] == 'FOOT' }
52
44
  footer.template :boundary
53
45
  footer.column :record_count, 10
54
46
  end
data/TODO CHANGED
@@ -1,4 +1,13 @@
1
- * Validation
2
- * Alternate Section Flow (other than linear), i.e. repeatable sections (think batch)
1
+ == 0.99.2
2
+
3
+ * Add :limit option on sections
4
+ * Add :validation option for columns
5
+ * Add a validate_file() method to parse a file and run all validation tests (implies validation implemented)
6
+
7
+ == 1.0.0
8
+
3
9
  * Better Documentation
4
- * Limit on section
10
+
11
+ == 1.x
12
+
13
+ * Alternate Section Flow (other than linear), i.e. repeatable sections (think batch)
@@ -2,7 +2,7 @@ require 'date'
2
2
 
3
3
  class Slither
4
4
  class Column
5
- attr_reader :name, :length, :alignment, :type, :options
5
+ attr_reader :name, :length, :alignment, :type, :padding, :precision, :options
6
6
 
7
7
  def initialize(name, length, options = {})
8
8
  assert_valid_options(options)
@@ -11,16 +11,15 @@ class Slither
11
11
  @options = options
12
12
  @alignment = options[:align] || :right
13
13
  @type = options[:type] || :string
14
- end
15
-
16
- def formatter
17
- "%#{aligner}#{length}s"
14
+ @padding = options[:padding] || :space
15
+ # Only used with floats, this determines the decimal places
16
+ @precision = options[:precision]
18
17
  end
19
18
 
20
19
  def unpacker
21
20
  "A#{@length}"
22
21
  end
23
-
22
+
24
23
  def to_type(value)
25
24
  case @type
26
25
  when :integer: value.to_i
@@ -35,28 +34,65 @@ class Slither
35
34
  end
36
35
  end
37
36
 
38
- def format_string(value)
39
- case @type
40
- when :date:
41
- if @options[:date_format]
42
- value.strftime(@options[:date_format])
43
- else
44
- value.strftime
45
- end
46
- else value.to_s
47
- end
37
+ def format(value)
38
+ pad(formatter % format_as_string(value))
48
39
  end
49
-
40
+
50
41
  private
51
42
 
43
+ def formatter
44
+ "%#{aligner}#{sizer}#{typer}"
45
+ end
46
+
52
47
  def aligner
53
48
  @alignment == :left ? '-' : ''
54
49
  end
55
50
 
51
+ def sizer
52
+ (@type == :float && @precision) ? @precision : @length
53
+ end
54
+
55
+ def typer
56
+ case @type
57
+ when :integer: 'd'
58
+ when :float: 's'
59
+ else 's'
60
+ end
61
+ end
62
+
63
+ # Manually apply padding. sprintf only allows padding on numeric fields.
64
+ def pad(value)
65
+ return value unless @padding == :zero
66
+ matcher = @alignment == :right ? /^ +/ : / +$/
67
+ space = value.match(matcher)
68
+ return value unless space
69
+ value.gsub(space[0], '0' * space[0].size)
70
+ end
71
+
72
+ def format_as_string(value)
73
+ result = case @type
74
+ when :date:
75
+ if @options[:date_format]
76
+ value.strftime(@options[:date_format])
77
+ else
78
+ value.strftime
79
+ end
80
+ else value.to_s
81
+ end
82
+ raise(
83
+ Slither::FormattedStringExceedsLengthError,
84
+ "The formatted value '#{result}' exceeds #{@length} chararacters, the allowable length of the '#{@name}' column."
85
+ ) if result.length > @length
86
+ result
87
+ end
88
+
56
89
  def assert_valid_options(options)
57
90
  unless options[:align].nil? || [:left, :right].include?(options[:align])
58
91
  raise ArgumentError, "Option :align only accepts :right (default) or :left"
59
92
  end
93
+ unless options[:padding].nil? || [:space, :zero].include?(options[:padding])
94
+ raise ArgumentError, "Option :padding only accepts :space (default) or :zero"
95
+ end
60
96
  end
61
97
  end
62
98
  end
@@ -10,8 +10,8 @@ class Slither
10
10
 
11
11
  def section(name, options = {}, &block)
12
12
  raise( ArgumentError, "Reserved or duplicate section name: '#{name}'") if
13
- Section::RESERVED_NAMES.include?( name ) ||
14
- (@sections.size > 0 && @sections.map{ |s| s.name }.include?( name ))
13
+ Section::RESERVED_NAMES.include?( name ) ||
14
+ (@sections.size > 0 && @sections.map{ |s| s.name }.include?( name ))
15
15
 
16
16
  section = Slither::Section.new(name, @options.merge(options))
17
17
  section.definition = self
@@ -27,7 +27,7 @@ class Slither
27
27
  end
28
28
 
29
29
  def method_missing(method, *args, &block)
30
- section(method, *args, &block)
30
+ section(method, *args, &block)
31
31
  end
32
32
  end
33
33
  end
@@ -1,7 +1,4 @@
1
1
  class Slither
2
-
3
- class RequiredSectionEmptyError < StandardError; end
4
-
5
2
  class Generator
6
3
 
7
4
  def initialize(definition)
@@ -14,12 +11,12 @@ class Slither
14
11
  content = data[section.name]
15
12
  if content
16
13
  content = [content] unless content.is_a?(Array)
17
- raise Slither::RequiredSectionEmptyError if content.empty?
14
+ raise(Slither::RequiredSectionEmptyError, "Required section '#{section.name}' was empty.") if content.empty?
18
15
  content.each do |row|
19
16
  @builder << section.format(row)
20
17
  end
21
18
  else
22
- raise Slither::RequiredSectionEmptyError unless section.optional
19
+ raise(Slither::RequiredSectionEmptyError, "Required section '#{section.name}' was empty.") unless section.optional
23
20
  end
24
21
  end
25
22
  @builder.join("\n")
@@ -1,7 +1,4 @@
1
1
  class Slither
2
-
3
- class RequiredSectionNotFoundError < StandardError; end
4
-
5
2
  class Parser
6
3
 
7
4
  def initialize(definition, file)
@@ -17,7 +14,7 @@ class Slither
17
14
  unless @content.empty?
18
15
  @definition.sections.each do |section|
19
16
  rows = fill_content(section)
20
- raise Slither::RequiredSectionNotFoundError unless rows > 0 || section.optional
17
+ raise(Slither::RequiredSectionNotFoundError, "Required section '#{section.name}' was not found.") unless rows > 0 || section.optional
21
18
  end
22
19
  end
23
20
  @parsed
@@ -14,6 +14,9 @@ class Slither
14
14
  end
15
15
 
16
16
  def column(name, length, options = {})
17
+ raise(Slither::DuplicateColumnNameError, "You have already defined a column named '#{name}'.") if @columns.map do |c|
18
+ RESERVED_NAMES.include?(c.name) ? nil : c.name
19
+ end.flatten.include?(name)
17
20
  col = Column.new(name, length, @options.merge(options))
18
21
  @columns << col
19
22
  col
@@ -38,7 +41,7 @@ class Slither
38
41
  def format(data)
39
42
  row = ''
40
43
  @columns.each do |column|
41
- row += (column.formatter % column.format_string(data[column.name]))
44
+ row += column.format(data[column.name])
42
45
  end
43
46
  row
44
47
  end
@@ -55,6 +58,10 @@ class Slither
55
58
  def match(raw_line)
56
59
  raw_line.nil? ? false : @trap.call(raw_line)
57
60
  end
61
+
62
+ def method_missing(method, *args)
63
+ column(method, *args)
64
+ end
58
65
 
59
66
  private
60
67
 
@@ -2,6 +2,12 @@ class Slither
2
2
 
3
3
  VERSION = '0.99.0'
4
4
 
5
+ class DuplicateColumnNameError < StandardError; end
6
+ class RequiredSectionNotFoundError < StandardError; end
7
+ class RequiredSectionEmptyError < StandardError; end
8
+ class FormattedStringExceedsLengthError < StandardError; end
9
+
10
+
5
11
  def self.define(name, options = {}, &block)
6
12
  definition = Definition.new(options)
7
13
  yield(definition)
data/slither.gemspec CHANGED
Binary file
data/spec/column_spec.rb CHANGED
@@ -16,12 +16,16 @@ describe Slither::Column do
16
16
  @column.length.should == @length
17
17
  end
18
18
 
19
+ it "should have a default padding" do
20
+ @column.padding.should == :space
21
+ end
22
+
19
23
  it "should have a default alignment" do
20
24
  @column.alignment.should == :right
21
25
  end
22
26
 
23
- it "should return a proper formatter" do
24
- @column.formatter.should == "%5s"
27
+ it "should return a proper formatter" do
28
+ @column.send(:formatter).should == "%5s"
25
29
  end
26
30
  end
27
31
 
@@ -37,14 +41,24 @@ describe Slither::Column do
37
41
  it "should override the default alignment" do
38
42
  @column.alignment.should == :left
39
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
40
50
 
41
- it "should return a proper formatter" do
42
- @column.formatter.should == "%-5s"
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
43
57
  end
44
58
  end
45
59
 
46
60
  it "should return the proper unpack value for a string" do
47
- @column.unpacker.should == 'A5'
61
+ @column.send(:unpacker).should == 'A5'
48
62
  end
49
63
 
50
64
  describe "when typing the value" do
@@ -77,31 +91,77 @@ describe Slither::Column do
77
91
  end
78
92
  end
79
93
 
80
- describe "when formatting the value" do
94
+ describe "when getting the column's the value as a string" do
81
95
  it "should default to a string" do
82
- @column.format_string('name').should == 'name'
96
+ @column.send(:format_as_string, 'name').should == 'name'
97
+ end
98
+
99
+ it "should raise an error if the value is longer than the length" do
100
+ lambda { @column.send(:format_as_string, 'This string is too long') }.should raise_error(
101
+ Slither::FormattedStringExceedsLengthError,
102
+ "The formatted value 'This string is too long' exceeds #{@length} chararacters, the allowable length of the '#{@name}' column."
103
+ )
83
104
  end
84
105
 
85
106
  it "should support the :integer type" do
86
107
  @column = Slither::Column.new(@name, @length, :type => :integer)
87
- @column.format_string(234).should == '234'
108
+ @column.send(:format_as_string, 234).should == '234'
88
109
  end
89
110
 
90
111
  it "should support the :float type" do
91
- @column = Slither::Column.new(@name, @length, :type => :float)
92
- @column.format_string(234.45).should == '234.45'
112
+ @column = Slither::Column.new(:amount, 6, :type => :float)
113
+ @column.send(:format_as_string, 234.45).should == '234.45'
93
114
  end
94
115
 
95
116
  it "should support the :date type" do
96
117
  dt = Date.new(2009, 8, 22)
97
- @column = Slither::Column.new(@name, @length, :type => :date)
98
- @column.format_string(dt).should == '2009-08-22'
118
+ @column = Slither::Column.new(:date, 10, :type => :date)
119
+ @column.send(:format_as_string, dt).should == '2009-08-22'
99
120
  end
100
121
 
101
122
  it "should use the :date_format option with :date type if available" do
102
123
  dt = Date.new(2009, 8, 22)
103
- @column = Slither::Column.new(@name, @length, :type => :date, :date_format => "%m%d%Y")
104
- @column.format_string(dt).should == '08222009'
124
+ @column = Slither::Column.new(:date, 8, :type => :date, :date_format => "%m%d%Y")
125
+ @column.send(:format_as_string, dt).should == '08222009'
105
126
  end
106
127
  end
128
+
129
+ describe "when formatting a column" do
130
+ it "should return a proper formatter" do
131
+ @column = Slither::Column.new(@name, @length, :align => :left)
132
+ @column.send(:formatter).should == "%-5s"
133
+ end
134
+
135
+ it "should respect a right alignment" do
136
+ @column = Slither::Column.new(@name, @length, :align => :right)
137
+ @column.format(25).should == ' 25'
138
+ end
139
+
140
+ it "should respect a left alignment" do
141
+ @column = Slither::Column.new(@name, @length, :align => :left)
142
+ @column.format(25).should == '25 '
143
+ end
144
+
145
+ it "should respect padding with spaces" do
146
+ @column = Slither::Column.new(@name, @length, :padding => :space)
147
+ @column.format(25).should == ' 25'
148
+ end
149
+
150
+ it "should respect padding with zeros with integer types" do
151
+ @column = Slither::Column.new(@name, @length, :type => :integer, :padding => :zero)
152
+ @column.format(25).should == '00025'
153
+ end
154
+
155
+ describe "that is a float type" do
156
+ it "should respect padding with zeros aligned right" do
157
+ @column = Slither::Column.new(@name, @length, :type => :float, :padding => :zero, :align => :right)
158
+ @column.format(4.45).should == '04.45'
159
+ end
160
+
161
+ it "should respect padding with zeros aligned left" do
162
+ @column = Slither::Column.new(@name, @length, :type => :float, :padding => :zero, :align => :left)
163
+ @column.format(4.45).should == '4.450'
164
+ end
165
+ end
166
+ end
107
167
  end
@@ -32,7 +32,7 @@ describe Slither::Generator do
32
32
 
33
33
  it "should raise an error if there is no data for a required section" do
34
34
  @data.delete :header
35
- lambda { @generator.generate(@data) }.should raise_error(Slither::RequiredSectionEmptyError)
35
+ lambda { @generator.generate(@data) }.should raise_error(Slither::RequiredSectionEmptyError, "Required section 'header' was empty.")
36
36
  end
37
37
 
38
38
  it "should generate a string" do
data/spec/parser_spec.rb CHANGED
@@ -66,7 +66,7 @@ describe Slither::Parser do
66
66
 
67
67
  it "should raise an error if a required section is not found" do
68
68
  @file.should_receive(:gets).twice.and_return(' Ryan Wood', nil)
69
- lambda { @parser.parse }.should raise_error(Slither::RequiredSectionNotFoundError)
69
+ lambda { @parser.parse }.should raise_error(Slither::RequiredSectionNotFoundError, "Required section 'header' was not found.")
70
70
  end
71
71
 
72
72
  it "raise an error if a section limit is over run"
data/spec/section_spec.rb CHANGED
@@ -13,28 +13,46 @@ describe Slither::Section do
13
13
  Slither::Section::RESERVED_NAMES.should == [:spacer]
14
14
  end
15
15
 
16
- it "should build an ordered column list" do
17
- @section.should have(0).columns
16
+ describe "when adding columns" do
17
+ it "should build an ordered column list" do
18
+ @section.should have(0).columns
18
19
 
19
- col1 = @section.column :id, 10
20
- col2 = @section.column :name, 30
21
- col3 = @section.column :state, 2
20
+ col1 = @section.column :id, 10
21
+ col2 = @section.column :name, 30
22
+ col3 = @section.column :state, 2
22
23
 
23
- @section.should have(3).columns
24
- @section.columns[0].should be(col1)
25
- @section.columns[1].should be(col2)
26
- @section.columns[2].should be(col3)
27
- end
24
+ @section.should have(3).columns
25
+ @section.columns[0].should be(col1)
26
+ @section.columns[1].should be(col2)
27
+ @section.columns[2].should be(col3)
28
+ end
28
29
 
29
- it "should create spacer columns" do
30
- @section.should have(0).columns
31
- @section.spacer(5)
32
- @section.should have(1).columns
33
- end
30
+ it "should create spacer columns" do
31
+ @section.should have(0).columns
32
+ @section.spacer(5)
33
+ @section.should have(1).columns
34
+ end
34
35
 
35
- it "can should override the alignment of the definition" do
36
- section = Slither::Section.new('name', :align => :left)
37
- section.options[:align].should == :left
36
+ it "can should override the alignment of the definition" do
37
+ section = Slither::Section.new('name', :align => :left)
38
+ section.options[:align].should == :left
39
+ end
40
+
41
+ it "should use a missing method to create a column" do
42
+ @section.should have(0).columns
43
+ @section.first_name 5
44
+ @section.should have(1).columns
45
+ end
46
+
47
+ it "should prevent duplicate column names" do
48
+ @section.column :id, 10
49
+ lambda { @section.column(:id, 30) }.should raise_error(Slither::DuplicateColumnNameError, "You have already defined a column named 'id'.")
50
+ end
51
+
52
+ it "should allow duplicate column names that are reserved (i.e. spacer)" do
53
+ @section.spacer 10
54
+ lambda { @section.spacer 10 }.should_not raise_error(Slither::DuplicateColumnNameError)
55
+ end
38
56
  end
39
57
 
40
58
  it "should accept and store the trap as a block" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ryanwood-slither
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.99.0
4
+ version: 0.99.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Wood