fencer 0.4.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.
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