Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repeatable Sections #10

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.rdoc
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,38 @@ or parse files already in that format into a nested hash:

parsed_data = Slither.parse('infile.txt', :test).inspect

== Repeating Sections

To enable repeating sections simple define the prefix you want for the repeating keys in the hash and pass the
repeatable option to the section that is repeatable.

Slither.define :sac_bee, :repeater => 'r' do |d|
d.header do |h|
h.trap { |line| line[0] == '1' }
h.column :header_begin, 1
h.column :destination
h.column :payment_deposit_date, 6
end
d.payment_records :repeatable => true do |pr|
pr.trap { |line| line[0] == '6' }
pr.column :payment_amount, 10
end
d.batch_trailer_record :repeatable => true do |btr|
btr.trap { |line| line[0] == '7' }
btr.column :beginning_of_batch_trailer_record, 1
btr.column :total_count_of_records_in_batch, 4
btr.column :total_payment_amount_of_batch, 10
end
d.file_trailer_record do |ftr|
ftr.trap { |line| line[0] == '8' }
ftr.column :beginning_of_trailer_record, 1
ftr.column :total_count_of_records, 5
ftr.column :total_payment_amount, 10
end
end

The data will be parsed into a hash as above but will have keys like "payment_records#{repeater}{num}" for each repeating section.

== INSTALL:

sudo gem install slither
Expand Down
Empty file modified lib/slither/column.rb
100644 → 100755
Empty file.
15 changes: 14 additions & 1 deletion lib/slither/definition.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
class Slither
class Definition
attr_reader :sections, :templates, :options
attr_reader :sections, :templates, :options, :repeater

def initialize(options = {})
@sections = []
@templates = {}
@repeater = options[:repeater] || 'r'
@options = { :align => :right }.merge(options)
end

Expand All @@ -26,6 +27,18 @@ def template(name, options = {}, &block)
@templates[name] = section
end

def find_section(name)
@sections.select{|section| section.name.to_sym == name}.first rescue nil
end

def repeatable_sections
@sections.select{|section| section.repeatable}
end

def non_repeatable_sections
@sections.select{|section| !section.repeatable}
end

def method_missing(method, *args, &block)
section(method, *args, &block)
end
Expand Down
24 changes: 13 additions & 11 deletions lib/slither/generator.rb
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ def initialize(definition)

def generate(data)
@builder = []
@definition.sections.each do |section|
content = data[section.name]
if content
content = [content] unless content.is_a?(Array)
raise(Slither::RequiredSectionEmptyError, "Required section '#{section.name}' was empty.") if content.empty?
content.each do |row|
@builder << section.format(row)
end
else
raise(Slither::RequiredSectionEmptyError, "Required section '#{section.name}' was empty.") unless section.optional
end
data.each do |section_name,content|
#remove #{@definition.repeater}[number] which was created by repeating rows
repeat_regex = Regexp.new("#{@definition.repeater}{1}\\d+\\z")
section_name = section_name.to_s.gsub(repeat_regex, '')

section = @definition.find_section(section_name.to_sym)
raise(Slither::UndefinedSectionError, "Undefined section '#{section_name}'.") if section.nil?

content = [content] unless content.is_a?(Array)
raise(Slither::UknownSectionError, "Required section '#{section.name}' was empty.") if content.empty?
content.each do |row|
@builder << section.format(row)
end
end
@builder.join("\n")
end
Expand Down
47 changes: 40 additions & 7 deletions lib/slither/parser.rb
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ class Parser

def initialize(definition, file)
@definition = definition
@repeat_counter = 1
@repeating_section = nil
@file = file
# This may be used in the future for non-linear or repeating sections
@mode = :linear
Expand All @@ -12,9 +14,22 @@ def parse()
@parsed = {}
@content = read_file
unless @content.empty?
@definition.sections.each do |section|
@definition.non_repeatable_sections.each do |section|
rows = fill_content(section)
raise(Slither::RequiredSectionNotFoundError, "Required section '#{section.name}' was not found.") unless rows > 0 || section.optional

#if no matches were found this might be a repeatable section
if rows == 0
repeatable_rows = parse_repeatable_sections
while repeatable_rows > 0
repeatable_rows = parse_repeatable_sections
end
rows = fill_content(section)
end

if rows == 0 and !section.optional
raise(Slither::RequiredSectionNotFoundError, "Required section '#{section.name}' was not found.")
end

end
end
@parsed
Expand All @@ -32,21 +47,39 @@ def read_file
content
end

def fill_content(section)
def fill_content(section, repeatable=false)
matches = 0
repeat_section_name = nil

loop do
line = @content.first
break unless section.match(line)
add_to_section(section, line)
if repeatable
unless @repeating_section == section.name
@repeating_section = section.name
repeat_section_name = "#{section.name}#{@definition.repeater}#{@repeat_counter}"
@repeat_counter += 1
end
end
add_to_section(section, line, repeatable, repeat_section_name)
matches += 1
@content.shift
end
matches
end

def add_to_section(section, line)
@parsed[section.name] = [] unless @parsed[section.name]
@parsed[section.name] << section.parse(line)
def parse_repeatable_sections
matches = 0
@definition.repeatable_sections.each do |section|
matches = fill_content(section, true)
end
matches
end

def add_to_section(section, line, repeatable, repeat_section_name)
key = repeatable ? repeat_section_name.to_sym : section.name
@parsed[key] = [] unless @parsed[key]
@parsed[key] << section.parse(line)
end

end
Expand Down
3 changes: 2 additions & 1 deletion lib/slither/section.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Slither
class Section
attr_accessor :definition, :optional
attr_accessor :definition, :optional, :repeatable
attr_reader :name, :columns, :options

RESERVED_NAMES = [:spacer]
Expand All @@ -11,6 +11,7 @@ def initialize(name, options = {})
@columns = []
@trap = options[:trap]
@optional = options[:optional] || false
@repeatable = options[:repeatable] || false
end

def column(name, length, options = {})
Expand Down
2 changes: 2 additions & 0 deletions lib/slither/slither.rb
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class RequiredSectionEmptyError < StandardError; end
class FormattedStringExceedsLengthError < StandardError; end
class ColumnMismatchError < StandardError; end

class UndefinedSectionError < StandardError; end


def self.define(name, options = {}, &block)
definition = Definition.new(options)
Expand Down
68 changes: 68 additions & 0 deletions spec/definition_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,74 @@
d.section('name', :align => :left) {}
end
end

describe "when specifying repeater" do
it "should have a repeater option" do
d = Slither::Definition.new :repeater => 'r'
d.repeater.should == 'r'
end

it "should should default repeater to r" do
d = Slither::Definition.new
d.repeater.should == 'r'
end

it "should should allow you get all repeatable sections" do
d = Slither::Definition.new

d.section :header do |section|
end

d.section :body, :repeatable => true do |section|
end

d.section :footer do |section|
end

d.repeatable_sections.count.should eq 1
end

it "should should allow you get all non repeatable sections" do
d = Slither::Definition.new

d.section :header do |section|
end

d.section :body, :repeatable => true do |section|
end

d.section :footer do |section|
end

d.non_repeatable_sections.count.should eq 2
end

it "should should allow you get all non repeatable sections" do
d = Slither::Definition.new

d.section :header do |section|
end

d.section :body, :repeatable => true do |section|
end

d.section :footer do |section|
end

d.non_repeatable_sections.count.should eq 2
end

it "should allow you get a section by name" do
d = Slither::Definition.new

d.section :header do |section|
end

section = d.find_section(:header)
section.should be_a(Slither::Section)
section.name.should eq :header
end
end

describe "when creating a section" do
before(:each) do
Expand Down
55 changes: 54 additions & 1 deletion spec/generator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,58 @@
it "should generate a string" do
expected = "HEAD 1\n Paul Hewson\n Dave Evans\nFOOT 1"
@generator.generate(@data).should == expected
end
end

describe "when repeating sections" do
before(:all) do
@repeat_definition = Slither.define :simple, :repeater => 'r' do |d|
d.header do |header|
header.trap { |line| line[0,1] == 1 }
header.column :header_begin, 1
header.column :batch_number, 3, :padding => :zero
end
d.data :repeatable => true do |data|
data.trap { |line| line[0,1] == 2 }
data.column :data_begin, 1
data.column :record_number, 3
data.column :record_number_plus_batch, 6
data.column :id, 2
data.column :name, 10, :align => :left
end
d.tail_record :repeatable => true do |tail_record|
tail_record.trap { |line| line[0,1] == 3 }
tail_record.column :tail_record_begin, 1
tail_record.column :num_records, 3
end
d.footer do |footer|
footer.trap { |line| line[0,1] == 4 }
footer.column :footer_record_begin, 1
footer.column :total_record_count, 3
footer.column :batch_number, 3, :padding => :zero
end
end
@repeat_data = {
:header => [ {:header_begin => 1, :batch_number => 001 }],
:datar1 => [
{:data_begin => 2, :record_number => '001',
:record_number_plus_batch => '001001', :id => '01', :name => 'Russell' },
{:data_begin => 2, :record_number => '002',
:record_number_plus_batch => '002001', :id => '02', :name => 'John' },
],
:tail_recordr1 => [:tail_record_begin => 3, :num_records => '002'],
:datar2 => [
{:data_begin => 2, :record_number => '001',
:record_number_plus_batch => '001001', :id => '01', :name => 'Bill' }
],
:tail_recordr2 => [:tail_record_begin => 3, :num_records => '001'],
:footer => [ {:footer_record_begin => 4, :total_record_count => "003", :batch_number => '001' }]
}
@repeat_generator = Slither::Generator.new(@repeat_definition)
end

it "should generate a string" do
expected = "1001\n200100100101Russell \n200200200102John \n3002\n200100100101Bill \n3001\n4003001"
@repeat_generator.generate(@repeat_data).should == expected
end
end
end
Loading