fencer 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color --format documentation
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Dan Cheail
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,66 @@
1
+ # Fencer
2
+ Fencer is designed to process rows of fixed-length and delimited text
3
+ data, splitting at designated termination points, converting field values
4
+ where required and making fields available through named object accessors.
5
+
6
+ Row formats are defined by subclassing Fencer::Base and using the DSL
7
+ provided.
8
+
9
+ ## Example
10
+ class EmployeeRecord < Fencer::Base
11
+ field :department, 50, :string
12
+ field :name, 20, -> s { s.split }
13
+ space 2
14
+ field :age, 4, :integer
15
+ end
16
+
17
+ `field` takes 3 arguments: a field name, the field length and an (optional)
18
+ converter.
19
+
20
+ ## Field Conversion
21
+ `Fencer::Base::Converters` is a `Hash<` defines some commonly-used converters.
22
+ It's left un-frozen, so it can be extended as required.
23
+
24
+ Short-cut methods for the default field types are also available:
25
+
26
+ class EmployeeRecord < Fencer::Base
27
+ string :department, 20 => String
28
+ integer :age, 2 => Integer
29
+ decimal :salary, 10 => BigDecimal
30
+ end
31
+
32
+ Additionally, custom conversions can be defined by passing a `lambda`
33
+ as the final argument.
34
+
35
+ ## Usage
36
+
37
+ Records are extracted on initialisation:
38
+
39
+ raw_string = "EXAMPLE FORMAT 10 300.04"
40
+ fields = EmployeeRecord.new(raw_string)
41
+
42
+ And are directly accessible thereafter:
43
+
44
+ fields.department # => "EXAMPLE FORMAT"
45
+ fields.age # => 2
46
+ fields.salary # => BigDecimal("300.04")
47
+
48
+ In the case of importing delimiter-separated data, passing the delimiting
49
+ character as the second argument to `new` will yield the desired result
50
+ without any change of layout:
51
+
52
+ raw_string = "EXAMPLE FORMAT|10|300.04"
53
+ fields = EmployeeRecord.new(raw_string, "|")
54
+
55
+ fields.department # => "EXAMPLE FORMAT"
56
+ fields.age # => 2
57
+ fields.salary # => BigDecimal("300.04")
58
+
59
+
60
+ ## Known Deficiencies
61
+
62
+ Currently, Fencer works with Ruby 1.9 only. Sorry. I wanted Hashes that
63
+ preserve field-order. P;us, the newer syntax is pretty.
64
+
65
+ Fencer is also blissfully unaware of any sort of encoding. This is a planned
66
+ feature for the 1.0 release.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/fencer/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Dan Cheail"]
6
+ gem.email = ["dan@undumb.com"]
7
+ gem.description = %q{Fixed-length/delimited record parser DSL}
8
+ gem.summary = %q{Fencer makes working with fixed-length and delimited
9
+ text-based records simpler by providing a flexible DSL
10
+ for defining field lengths and transformations}
11
+
12
+ gem.homepage = "https://github.com/undumb/fencer"
13
+ gem.files = `git ls-files`.split($\) - %w(Gemfile .gitignore)
14
+ # gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.name = "fencer"
17
+ gem.require_paths = ["lib"]
18
+ gem.version = Fencer::VERSION
19
+
20
+ gem.add_development_dependency "rspec", "~> 2.10"
21
+ gem.add_development_dependency "rake"
22
+ end
@@ -0,0 +1,89 @@
1
+ require "bigdecimal"
2
+ require "fencer/version"
3
+
4
+ module Fencer
5
+
6
+ class Base
7
+ Converters = {
8
+ string: -> s { s.strip },
9
+ integer: -> s { s.to_i },
10
+ decimal: -> s { BigDecimal(s) },
11
+ }
12
+
13
+ class << self
14
+ attr_reader :fields
15
+
16
+ def inherited(subclass)
17
+ subclass.instance_variable_set(:@fields, {})
18
+ end
19
+
20
+ def field(name, size, convert = nil)
21
+ # error handling, ahoy!
22
+ raise "#{name} already defined as a field on #{self.name}" if fields.has_key?(name)
23
+
24
+ unless convert.nil? || Converters.has_key?(convert) || convert.is_a?(Proc)
25
+ raise "Invalid converter"
26
+ end
27
+
28
+ fields[name] = { size: size, convert: convert }
29
+
30
+ # create our attr method
31
+ define_method(name) { @values[name] }
32
+ end
33
+
34
+ def space(size)
35
+ fields[:"_#{fields.length.succ}"] = { size: size, space: true }
36
+ end
37
+
38
+ def string(name, size)
39
+ field(name, size, :string)
40
+ end
41
+
42
+ def integer(name, size)
43
+ field(name, size, :integer)
44
+ end
45
+
46
+ def decimal(name, size)
47
+ field(name, size, :decimal)
48
+ end
49
+ end
50
+
51
+ def initialize(str, delimiter = nil)
52
+ @values = {}
53
+ @delimiter = delimiter
54
+ @str = str
55
+
56
+ parse!
57
+ end
58
+
59
+ def to_hash
60
+ @values
61
+ end
62
+
63
+ private
64
+
65
+ def parse!
66
+ if @delimiter
67
+ raw_values = @str.split(@delimiter)
68
+ else
69
+ unpack_phrase = self.class.fields.values.map { |s| "A#{s[:size]}" }.join
70
+ raw_values = @str.unpack(unpack_phrase)
71
+ end
72
+
73
+ _index = 0
74
+ self.class.fields.each do |name, opts|
75
+ unless opts[:space]
76
+ _conversion_proc = case opts[:convert]
77
+ when Symbol then Converters[opts[:convert]]
78
+ when Proc then opts[:convert]
79
+ else nil
80
+ end
81
+
82
+ @values[name] = _conversion_proc ? _conversion_proc.call(raw_values[_index]) : raw_values[_index]
83
+ end
84
+
85
+ _index += 1 unless opts[:space] && @delimiter
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ module Fencer
2
+ VERSION = "0.4.2"
3
+ end
@@ -0,0 +1,142 @@
1
+ # require 'rubygems'
2
+
3
+ Bundler.require(:default, :development)
4
+
5
+ require 'rspec'
6
+ require 'fencer'
7
+
8
+ describe Fencer do
9
+
10
+ class EmployeeRecord < Fencer::Base
11
+ string :name, 20
12
+ string :department, 20
13
+ space 2
14
+ field :employment_date, 8, -> s { dasherise_ymd(s) }
15
+ integer :id_number, 6
16
+ decimal :leave_accrued, 11
17
+
18
+ class << self
19
+ def dasherise_ymd(str)
20
+ str.gsub(/([0-9]{4})([0-9]{2})([0-9]{2})/) { "#{$1}-#{$2}-#{$3}" }
21
+ end
22
+ end
23
+ end
24
+
25
+ let(:name) { "Ford Prefect" }
26
+ let(:department) { "Publication" }
27
+ let(:employment_date) { "20120525" }
28
+ let(:id_number) { "42" }
29
+ let(:leave_accrued) { "1484.33" }
30
+
31
+ it "has many wonderful features" do
32
+
33
+ compiled_record = name.ljust(20)
34
+ compiled_record << department.ljust(20)
35
+ compiled_record << " "
36
+ compiled_record << employment_date
37
+ compiled_record << id_number.rjust(6, '0')
38
+ compiled_record << leave_accrued.rjust(11, '0')
39
+
40
+ # auto parsing
41
+ values = EmployeeRecord.new(compiled_record)
42
+
43
+ # individual accessors for each field
44
+ values.name.should eq(name)
45
+ values.department.should eq(department)
46
+ values.employment_date.should eq("2012-05-25")
47
+ values.id_number.should eq(id_number.to_i)
48
+ values.leave_accrued.should eq(BigDecimal(leave_accrued))
49
+
50
+ # export our values to a hash
51
+ values.to_hash.should eq({
52
+ name: name,
53
+ department: department,
54
+ employment_date: "2012-05-25",
55
+ id_number: id_number.to_i,
56
+ leave_accrued: BigDecimal(leave_accrued),
57
+ })
58
+ end
59
+
60
+ it "also works when parsing arbitrarily delimited fields!" do
61
+ compiled_record = [
62
+ name, department, employment_date, id_number, leave_accrued
63
+ ].join("|")
64
+
65
+ values = EmployeeRecord.new(compiled_record, "|")
66
+
67
+ values.name.should eq(name)
68
+ values.department.should eq(department)
69
+ values.employment_date.should eq("2012-05-25")
70
+ values.id_number.should eq(id_number.to_i)
71
+ values.leave_accrued.should eq(BigDecimal(leave_accrued))
72
+ end
73
+ end
74
+
75
+
76
+ class FencerBaseTest < Fencer::Base
77
+ class << self
78
+ def reset_fields!
79
+ @fields = {}
80
+ end
81
+ end
82
+ end
83
+
84
+ describe Fencer::Base do
85
+ before(:each) do
86
+ subject.reset_fields!
87
+ end
88
+
89
+ subject { FencerBaseTest }
90
+
91
+ it "#field adds fields to the internal register" do
92
+ subject.field(:derp, 1)
93
+ subject.fields.should have_key(:derp)
94
+ end
95
+
96
+ it "#field adds an instance access method" do
97
+ subject.field(:derp, 1)
98
+
99
+ instance = subject.new("")
100
+ instance.should respond_to(:derp)
101
+ instance.should_not respond_to(:herp)
102
+ end
103
+
104
+ it "raises an error when the size attribute is omitted" do
105
+ expect { subject.field(:derp) }.to raise_error
106
+ end
107
+
108
+ it "raises an error when duplicate keys are defined" do
109
+ subject.field(:derp, 1)
110
+ expect { subject.field(:derp, 1) }.to raise_error
111
+ end
112
+
113
+ context "when using shortcut methods" do
114
+ it "has a shortcut for string fields" do
115
+ subject.string(:derp, 1)
116
+ subject.fields.should have_key(:derp)
117
+ subject.fields[:derp][:convert].should be :string
118
+ end
119
+
120
+ it "has a shortcut for integer fields" do
121
+ subject.integer(:derp, 1)
122
+ subject.fields.should have_key(:derp)
123
+ subject.fields[:derp][:convert].should be :integer
124
+ end
125
+
126
+ it "has a shortcut for decimal fields" do
127
+ subject.decimal(:derp, 1)
128
+ subject.fields.should have_key(:derp)
129
+ subject.fields[:derp][:convert].should be :decimal
130
+ end
131
+ end
132
+
133
+ context "when setting an invalid conversion argument" do
134
+ it "raises an error if the symbol is not registered" do
135
+ expect { subject.field(:derp, 1, :to_date) }.to raise_error
136
+ end
137
+
138
+ it "raises an error if the argument is not a lambda" do
139
+ expect { subject.field(:derp, 1, "") }.to raise_error
140
+ end
141
+ end
142
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fencer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dan Cheail
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70270773693060 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.10'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70270773693060
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70270773692560 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70270773692560
36
+ description: Fixed-length/delimited record parser DSL
37
+ email:
38
+ - dan@undumb.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .rspec
44
+ - LICENSE
45
+ - README.md
46
+ - Rakefile
47
+ - fencer.gemspec
48
+ - lib/fencer.rb
49
+ - lib/fencer/version.rb
50
+ - spec/fencer_spec.rb
51
+ homepage: https://github.com/undumb/fencer
52
+ licenses: []
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ segments:
64
+ - 0
65
+ hash: 1981968692186319614
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ segments:
73
+ - 0
74
+ hash: 1981968692186319614
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 1.8.17
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Fencer makes working with fixed-length and delimited text-based records simpler
81
+ by providing a flexible DSL for defining field lengths and transformations
82
+ test_files:
83
+ - spec/fencer_spec.rb