fixed_width-multibyte 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING ADDED
@@ -0,0 +1,10 @@
1
+ Copyright (c) 2010, Topspin Media Inc.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+ * Neither the name of the Topspin Media Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9
+
10
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/HISTORY ADDED
@@ -0,0 +1,17 @@
1
+ v????
2
+ =========================
3
+ * support for multibyte chars
4
+ * tested in ruby 1.8.7 and ruby 1.9.2
5
+
6
+ v0.1.1 (2010-05-29)
7
+ =========================
8
+ * column grouping (parsing and writing)
9
+ * :singular section option
10
+ * :nil_blank column option
11
+ * some rdoc for FixedWidth
12
+ * idiomatic syntax cleanup
13
+
14
+ v0.1.0 (2010-05-19)
15
+ =========================
16
+ * non-release (hence untagged)
17
+ * Forked from ryanwood/slither, renamed.
@@ -0,0 +1,149 @@
1
+ DESCRIPTION:
2
+ ============
3
+
4
+ A simple, clean DSL for describing, writing, and parsing fixed-width text files.
5
+
6
+ FEATURES:
7
+ =========
8
+
9
+ * Easy DSL syntax
10
+ * Can parse and format fixed width files
11
+ * Templated sections for reuse
12
+
13
+ SYNOPSIS:
14
+ =========
15
+
16
+ ##Creating a definition (Quick 'n Dirty)
17
+
18
+ Hopefully this will cover 90% of use cases.
19
+
20
+ # Create a FixedWidth::Defintion to describe a file format
21
+ FixedWidth.define :simple do |d|
22
+ # This is a template section that can be reused in other sections
23
+ d.template :boundary do |t|
24
+ t.column :record_type, 4
25
+ t.column :company_id, 12
26
+ end
27
+
28
+ # Create a section named :header
29
+ d.header(:align => :left) do |header|
30
+ # The trap tells FixedWidth which lines should fall into this section
31
+ header.trap { |line| line[0,4] == 'HEAD' }
32
+ # Use the boundary template for the columns
33
+ header.template :boundary
34
+ end
35
+
36
+ d.body do |body|
37
+ body.trap { |line| line[0,4] =~ /[^(HEAD|FOOT)]/ }
38
+ body.column :id, 10, :parser => :to_i
39
+ body.column :first, 10, :align => :left, :group => :name
40
+ body.column :last, 10, :align => :left, :group => :name
41
+ body.spacer 3
42
+ body.column :city, 20 , :group => :address
43
+ body.column :state, 2 , :group => :address
44
+ body.column :country, 3, :group => :address
45
+ end
46
+
47
+ d.footer do |footer|
48
+ footer.trap { |line| line[0,4] == 'FOOT' }
49
+ footer.template :boundary
50
+ footer.column :record_count, 10, :parser => :to_i
51
+ end
52
+ end
53
+
54
+ This definition would output a parsed file something like this:
55
+
56
+ {
57
+ :body => [
58
+ { :id => 12,
59
+ :name => { :first => "Ryan", :last => "Wood" },
60
+ :address => { :city => "Foo", :state => 'SC', :country => "USA" }
61
+ },
62
+ { :id => 13,
63
+ :name => { :first => "Jo", :last => "Schmo" },
64
+ :address => { :city => "Bar", :state => "CA", :country => "USA" }
65
+ }
66
+ ],
67
+ :header => [{ :record_type => 'HEAD', :company_id => 'ABC' }],
68
+ :footer => [{ :record_type => 'FOOT', :company_id => 'ABC', :record_count => 2 }]
69
+ }
70
+
71
+ ##Sections
72
+ ###Declaring a section
73
+
74
+ Sections can have any name, however duplicates are not allowed. (A `DuplicateSectionNameError` will be thrown.) We use the standard `method_missing` trick. So if you see any unusual behavior, that's probably the first spot to look.
75
+
76
+ FixedWidth.define :simple do |d|
77
+ d.a_section_name do |s|
78
+ ...
79
+ end
80
+ d.another_section_name do |s|
81
+ ...
82
+ end
83
+ end
84
+
85
+ ### Section options:
86
+
87
+ * `:singular` (default `false`) indicates that the section will only have a single record, and that it should not be returned nested in an array.
88
+
89
+ * `:optional` (default `false`) indicates that the section is optional. (An otherwise-specified section will raise a `RequiredSectionNotFoundError` if the trap block doesn't match the row after the last one of the previous section.)
90
+
91
+ ##Columns
92
+ ###Declaring a column
93
+
94
+ Columns can have any name, except for `:spacer` which is reserved. Also, duplicate column names within groupings are not allowed, and a column cannot share the same name as a group. (A `DuplicateColumnNameError` will be thrown for a duplicate column name within a grouping. A `DuplicateGroupNameError` will be thrown if you try to declare a column with the same name as an existing group or vice versa.) Again, basic `method_missing` trickery here, so be warned. You can declare columns either with the `method_missing` thing or by calling `Section#column`.
95
+
96
+ FixedWidth.define :simple do |d|
97
+ d.a_section_name do |s|
98
+ s.a_column_name 12
99
+ s.column :another_column_name, 14
100
+ end
101
+ end
102
+
103
+ ###Column Options:
104
+
105
+ * `:align` can be set to `:left` or `:right`, to indicate which side the values should be/are justified to. By default, all columns are aligned `:right`.
106
+
107
+ * `:group` can be set to a `Symbol` indicating the name of the nested hash which the value should be parsed to when reading/the name of the nested hash the value should be extracted from when writing.
108
+
109
+ * `:parser` and `:formatter` options are symbols (to be proc-ified) or procs. By default, parsing and formatting assume that we're expecting/writing right-aligned strings, padded with spaces.
110
+
111
+ * `:nil_blank` set to true will cause whitespace-only fields to be parsed to nil, regardless of `:parser`.
112
+
113
+ * `:padding` can be set to a single character that will be used to pad formatted values, when writing fixed-width files.
114
+
115
+ * `:truncate` can be set to true to truncate any value that exceeds the `length` property of a column. If unset or set to `false`, a `FixedWidth::FormattedStringExceedsLengthError` exception will be thrown.
116
+
117
+ ##Writing out fixed-width records
118
+
119
+ Then either feed it a nested struct with data values to create the file in the defined format:
120
+
121
+ test_data = {
122
+ :body => [
123
+ { :id => 12,
124
+ :name => { :first => "Ryan", :last => "Wood" },
125
+ :address => { :city => "Foo", :state => 'SC', :country => "USA" }
126
+ },
127
+ { :id => 13,
128
+ :name => { :first => "Jo", :last => "Schmo" },
129
+ :address => { :city => "Bar", :state => "CA", :country => "USA" }
130
+ }
131
+ ],
132
+ :header => [{ :record_type => 'HEAD', :company_id => 'ABC' }],
133
+ :footer => [{ :record_type => 'FOOT', :company_id => 'ABC', :record_count => 2 }]
134
+ }
135
+
136
+ # Generates the file as a string
137
+ puts FixedWidth.generate(:simple, test_data)
138
+
139
+ # Writes the file
140
+ FixedWidth.write(file_instance, :simple, test_data)
141
+
142
+ Or parse files already in that format into a nested hash:
143
+
144
+ parsed_data = FixedWidth.parse(file_instance, :test).inspect
145
+
146
+ INSTALL:
147
+ ========
148
+
149
+ sudo gem install fixed_width
@@ -0,0 +1,49 @@
1
+ task :default => :spec
2
+ task :test => :spec
3
+
4
+ desc "Build a gem"
5
+ task :gem => [ :gemspec, :build ]
6
+
7
+ desc "Run specs"
8
+ task :spec do
9
+ exec "spec -fn -b -c spec/"
10
+ end
11
+
12
+ begin
13
+ require 'jeweler'
14
+ Jeweler::Tasks.new do |gemspec|
15
+ gemspec.name = "fixed_width-multibyte"
16
+ gemspec.summary = "A gem that provides a DSL for parsing and writing files of fixed-width records."
17
+ gemspec.description = <<END
18
+ A gem that provides a DSL for parsing and writing files of fixed-width records.
19
+
20
+ Multibyte support added while Timon is on vacation.
21
+
22
+ Shamelessly forked from ryanwood/slither [http://github.com/ryanwood/slither].
23
+
24
+ Renamed the gem to be a little clearer as to its purpose. Hate that 'nokogiri' nonsense.
25
+ END
26
+ gemspec.email = "timon.karnezos@gmail.com"
27
+ gemspec.homepage = "http://github.com/timonk/fixed_width"
28
+ gemspec.authors = ["Timon Karnezos"]
29
+ gemspec.add_dependency 'activesupport'
30
+ end
31
+ rescue LoadError
32
+ warn "Jeweler not available. Install it with:"
33
+ warn "gem install jeweler"
34
+ end
35
+
36
+ require 'rake/rdoctask'
37
+ Rake::RDocTask.new do |rdoc|
38
+ if File.exist?('VERSION')
39
+ version = File.read('VERSION')
40
+ else
41
+ version = ""
42
+ end
43
+
44
+ rdoc.rdoc_dir = 'rdoc'
45
+ rdoc.title = "rprince #{version}"
46
+ rdoc.options << '--line-numbers' << '--inline-source'
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/TODO ADDED
@@ -0,0 +1,7 @@
1
+ * Add :limit option on sections
2
+ * Add :validation option for columns
3
+ * Add a validate_file() method to parse a file and run all validation tests (implies validation implemented)
4
+ * Better Documentation
5
+ * Alternate Section Flow (other than linear), i.e. repeatable sections (think batch)
6
+ * Add batched generation, so we don't turn into memory hogs
7
+ * Add batched parsing, so we don't turn into memory hogs
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.2
@@ -0,0 +1,62 @@
1
+ require 'stringio'
2
+ require File.join(File.dirname(__FILE__), "..", "lib", "fixed_width")
3
+
4
+ # Create a FixedWidth::Defintion to describe a file format
5
+ FixedWidth.define :simple do |d|
6
+ # This is a template section that can be reused in other sections
7
+ d.template :boundary do |t|
8
+ t.column :record_type, 4
9
+ t.column :company_id, 12
10
+ end
11
+
12
+ # Create a header section
13
+ d.header(:align => :left) do |header|
14
+ # The trap tells FixedWidth which lines should fall into this section
15
+ header.trap { |line| line[0,4] == 'HEAD' }
16
+ # Use the boundary template for the columns
17
+ header.template :boundary
18
+ end
19
+
20
+ d.body do |body|
21
+ body.trap { |line| line[0,4] =~ /[^(HEAD|FOOT)]/ }
22
+ body.column :id, 10, :parser => :to_i
23
+ body.column :first, 10, :align => :left, :group => :name
24
+ body.column :last, 10, :align => :left, :group => :name
25
+ body.spacer 3
26
+ body.column :city, 20 , :group => :address
27
+ body.column :state, 2 , :group => :address
28
+ body.column :country, 3, :group => :address
29
+ end
30
+
31
+ d.footer do |footer|
32
+ footer.trap { |line| line[0,4] == 'FOOT' }
33
+ footer.template :boundary
34
+ footer.column :record_count, 10, :parser => :to_i
35
+ end
36
+ end
37
+
38
+ test_data = {
39
+ :body => [
40
+ { :id => 12,
41
+ :name => { :first => "Ryan", :last => "Wood" },
42
+ :address => { :city => "Foo", :state => 'SC', :country => "USA" }
43
+ },
44
+ { :id => 13,
45
+ :name => { :first => "Jo", :last => "Schmo" },
46
+ :address => { :city => "Bar", :state => "CA", :country => "USA" }
47
+ }
48
+ ],
49
+ :header => [{ :record_type => 'HEAD', :company_id => 'ABC' }],
50
+ :footer => [{ :record_type => 'FOOT', :company_id => 'ABC', :record_count => 2 }]
51
+ }
52
+
53
+ # Generates the file as a string
54
+ generated = FixedWidth.generate(:simple, test_data)
55
+
56
+ sio = StringIO.new
57
+ sio.write(generated)
58
+ sio.rewind
59
+
60
+ parsed = FixedWidth.parse(sio, :simple)
61
+
62
+ puts parsed == test_data
@@ -0,0 +1,69 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{fixed_width-multibyte}
8
+ s.version = "0.2.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Timon Karnezos"]
12
+ s.date = %q{2011-05-21}
13
+ s.description = %q{A gem that provides a DSL for parsing and writing files of fixed-width records.
14
+
15
+ Multibyte support added while Timon is on vacation.
16
+
17
+ Shamelessly forked from ryanwood/slither [http://github.com/ryanwood/slither].
18
+
19
+ Renamed the gem to be a little clearer as to its purpose. Hate that 'nokogiri' nonsense.
20
+ }
21
+ s.email = %q{timon.karnezos@gmail.com}
22
+ s.extra_rdoc_files = [
23
+ "README.markdown",
24
+ "TODO"
25
+ ]
26
+ s.files = [
27
+ "COPYING",
28
+ "HISTORY",
29
+ "README.markdown",
30
+ "Rakefile",
31
+ "TODO",
32
+ "VERSION",
33
+ "examples/readme_example.rb",
34
+ "fixed_width-multibyte.gemspec",
35
+ "fixed_width.gemspec",
36
+ "lib/fixed_width.rb",
37
+ "lib/fixed_width/column.rb",
38
+ "lib/fixed_width/core_ext/symbol.rb",
39
+ "lib/fixed_width/definition.rb",
40
+ "lib/fixed_width/fixed_width.rb",
41
+ "lib/fixed_width/generator.rb",
42
+ "lib/fixed_width/parser.rb",
43
+ "lib/fixed_width/section.rb",
44
+ "spec/column_spec.rb",
45
+ "spec/definition_spec.rb",
46
+ "spec/fixed_width_spec.rb",
47
+ "spec/generator_spec.rb",
48
+ "spec/parser_spec.rb",
49
+ "spec/section_spec.rb",
50
+ "spec/spec_helper.rb"
51
+ ]
52
+ s.homepage = %q{http://github.com/timonk/fixed_width}
53
+ s.require_paths = ["lib"]
54
+ s.rubygems_version = %q{1.6.2}
55
+ s.summary = %q{A gem that provides a DSL for parsing and writing files of fixed-width records.}
56
+
57
+ if s.respond_to? :specification_version then
58
+ s.specification_version = 3
59
+
60
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
61
+ s.add_runtime_dependency(%q<activesupport>, [">= 0"])
62
+ else
63
+ s.add_dependency(%q<activesupport>, [">= 0"])
64
+ end
65
+ else
66
+ s.add_dependency(%q<activesupport>, [">= 0"])
67
+ end
68
+ end
69
+
@@ -0,0 +1,76 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{fixed_width}
8
+ s.version = "0.2.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Timon Karnezos"]
12
+ s.date = %q{2010-05-31}
13
+ s.description = %q{A gem that provides a DSL for parsing and writing files of fixed-width records.
14
+
15
+ Shamelessly forked from ryanwood/slither [http://github.com/ryanwood/slither].
16
+
17
+ Renamed the gem to be a little clearer as to its purpose. Hate that 'nokogiri' nonsense.
18
+ }
19
+ s.email = %q{timon.karnezos@gmail.com}
20
+ s.extra_rdoc_files = [
21
+ "README.markdown",
22
+ "TODO"
23
+ ]
24
+ s.files = [
25
+ ".gitignore",
26
+ "COPYING",
27
+ "HISTORY",
28
+ "README.markdown",
29
+ "Rakefile",
30
+ "TODO",
31
+ "VERSION",
32
+ "examples/readme_example.rb",
33
+ "fixed_width.gemspec",
34
+ "lib/fixed_width.rb",
35
+ "lib/fixed_width/column.rb",
36
+ "lib/fixed_width/core_ext/symbol.rb",
37
+ "lib/fixed_width/definition.rb",
38
+ "lib/fixed_width/fixed_width.rb",
39
+ "lib/fixed_width/generator.rb",
40
+ "lib/fixed_width/parser.rb",
41
+ "lib/fixed_width/section.rb",
42
+ "spec/column_spec.rb",
43
+ "spec/definition_spec.rb",
44
+ "spec/fixed_width_spec.rb",
45
+ "spec/generator_spec.rb",
46
+ "spec/parser_spec.rb",
47
+ "spec/section_spec.rb",
48
+ "spec/spec_helper.rb"
49
+ ]
50
+ s.homepage = %q{http://github.com/timonk/fixed_width}
51
+ s.rdoc_options = ["--charset=UTF-8"]
52
+ s.require_paths = ["lib"]
53
+ s.rubygems_version = %q{1.3.6}
54
+ s.summary = %q{A gem that provides a DSL for parsing and writing files of fixed-width records.}
55
+ s.test_files = [
56
+ "spec/column_spec.rb",
57
+ "spec/definition_spec.rb",
58
+ "spec/fixed_width_spec.rb",
59
+ "spec/generator_spec.rb",
60
+ "spec/parser_spec.rb",
61
+ "spec/section_spec.rb",
62
+ "spec/spec_helper.rb",
63
+ "examples/readme_example.rb"
64
+ ]
65
+
66
+ if s.respond_to? :specification_version then
67
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
68
+ s.specification_version = 3
69
+
70
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
71
+ else
72
+ end
73
+ else
74
+ end
75
+ end
76
+