fixy 0.0.1
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/.gitignore +19 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +175 -0
- data/Rakefile +11 -0
- data/fixy.gemspec +25 -0
- data/lib/fixy.rb +6 -0
- data/lib/fixy/decorator/debug.rb +65 -0
- data/lib/fixy/decorator/default.rb +19 -0
- data/lib/fixy/document.rb +40 -0
- data/lib/fixy/formatter/alphanumeric.rb +24 -0
- data/lib/fixy/record.rb +107 -0
- data/lib/fixy/version.rb +3 -0
- data/spec/fixtures/debug_document.txt +50 -0
- data/spec/fixtures/debug_record.txt +2 -0
- data/spec/fixy/document_spec.rb +42 -0
- data/spec/fixy/record_spec.rb +96 -0
- data/spec/spec_helper.rb +2 -0
- metadata +134 -0
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Omar Skalli
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
## fixy
|
2
|
+
|
3
|
+
Library for generating fixed width flat file documents.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'fixy'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
```bash
|
17
|
+
bundle
|
18
|
+
```
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
```bash
|
23
|
+
gem install fixy
|
24
|
+
```
|
25
|
+
|
26
|
+
Then proceed to creating your records, and documents as described in the paragraphs below.
|
27
|
+
|
28
|
+
## Overview
|
29
|
+
|
30
|
+
A fixed-width document (`Fixy::Document`) is composed of multiple single-line records (`Fixy::Record`).
|
31
|
+
|
32
|
+
## Record definition
|
33
|
+
|
34
|
+
Every record is defined through a specific format, which defines the following aspects:
|
35
|
+
|
36
|
+
* Record length (how many characters in the line)
|
37
|
+
* Required formatters (e.g. Alphanumeric, Rate, Amount)
|
38
|
+
* Field declaration:
|
39
|
+
* Field human readable name
|
40
|
+
* Field size (how many characters for the field)
|
41
|
+
* Field range (start/end column for the field)
|
42
|
+
* Field format (e.g. Alphanumeric, Rate, Amount)
|
43
|
+
* Field definition
|
44
|
+
|
45
|
+
Below is an example of a record for defining a person's first and last name:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class PersonRecord < Fixy::Record
|
49
|
+
|
50
|
+
# Include formatters
|
51
|
+
|
52
|
+
include Fixy::Formatter::Alphanumeric
|
53
|
+
|
54
|
+
# Define record length
|
55
|
+
|
56
|
+
set_record_length 20
|
57
|
+
|
58
|
+
# Fields Declaration:
|
59
|
+
# -----------------------------------------------------------
|
60
|
+
# name size Range Format
|
61
|
+
# ------------------------------------------------------------
|
62
|
+
|
63
|
+
field :first_name, 10, '1-10' , :alphanumeric
|
64
|
+
field :last_name , 10, '11-20', :alphanumeric
|
65
|
+
|
66
|
+
# Any required data for the record can be
|
67
|
+
# provided through the initializer
|
68
|
+
|
69
|
+
def initialize(first_name, last_name)
|
70
|
+
@first_name = first_name
|
71
|
+
@last_name = last_name
|
72
|
+
end
|
73
|
+
|
74
|
+
# Fields Definition:
|
75
|
+
# 1) Using a Proc
|
76
|
+
|
77
|
+
field_value :first_name, -> { @first_name }
|
78
|
+
|
79
|
+
# 2) Using a method definition.
|
80
|
+
# This is most interesting when complex logic is involved.
|
81
|
+
|
82
|
+
def last_name
|
83
|
+
@last_name
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
Given a record definition, you can generate a single line (e.g. for testing purposes):
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
PersonRecord.new('Sarah', 'Kerrigan').generate
|
92
|
+
|
93
|
+
# This will output the following 20 characters long record
|
94
|
+
#
|
95
|
+
# "Sarah Kerrigan \n"
|
96
|
+
#
|
97
|
+
```
|
98
|
+
|
99
|
+
Most of the time however, you will not have to call `generate` directly, as the document will take care of that part.
|
100
|
+
|
101
|
+
## Document definition
|
102
|
+
|
103
|
+
A document is composed of a multitude of records (instances of a `Fixy::Record`). Because some document specification require earlier records to contain a count of upcoming records, both appending and prepending records is supported during a document definition. Below is an example of a document, based on the record defined in the previous section.
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
class PeopleDocument < Fixy::Document
|
107
|
+
def build
|
108
|
+
append_record PersonRecord.new('Sarah', 'Kerrigan')
|
109
|
+
append_record PersonRecordnew('Jim', 'Raynor')
|
110
|
+
prepend_record PersonRecord.new('Arcturus', 'Mengsk')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
## Generating a document
|
116
|
+
|
117
|
+
With records and documents defined, generating documents is a breeze:
|
118
|
+
|
119
|
+
** Generating to string **
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
|
123
|
+
PeopleDocument.new.generate
|
124
|
+
```
|
125
|
+
|
126
|
+
The output would be: "Arcturus Mengsk \nSarah Kerrigan \nJim Raynor "
|
127
|
+
|
128
|
+
** Generating to file **
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
|
132
|
+
PeopleDocument.new.generate_to_file("output.txt")
|
133
|
+
```
|
134
|
+
|
135
|
+
** Generating HTML Debug version **
|
136
|
+
|
137
|
+
This is most useful when getting an error such as: `Unexpected character at line 20, column 95`. The HTML output makes it really easy to make sense out of any fixed width document, and quickly identify issues.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
|
141
|
+
PeopleDocument.new.generate_to_file("output.html", true)
|
142
|
+
```
|
143
|
+
|
144
|
+
|
145
|
+
## Creating custom formatters
|
146
|
+
|
147
|
+
Currently, there aren't many formatters included in this release, and you will most likely have to write your own. To create a new formatter of type `type` (e.g. amount), you simply need a method called `format_<type>(input, length)`. The argument `input` is the value being formatted, and `length` is the number of characters to fill. It is important to make sure `length` characters are returned by the formatter!
|
148
|
+
|
149
|
+
An example for formatter definition:
|
150
|
+
```ruby
|
151
|
+
|
152
|
+
module Fixy
|
153
|
+
module Formatter
|
154
|
+
module Numeric
|
155
|
+
def format_numeric(input, length)
|
156
|
+
input = input.to_s
|
157
|
+
raise ArgumentError, "Invalid Input (only digits are accepted)" unless input =~ /^\d+$/
|
158
|
+
raise ArgumentError, "Not enough length (input: #{input}, length: #{length})" if input.length > length
|
159
|
+
input.rjust(length, '0')
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
|
167
|
+
|
168
|
+
## Contributing
|
169
|
+
|
170
|
+
1. Fork it
|
171
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
172
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
173
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
174
|
+
5. Create new Pull Request
|
175
|
+
|
data/Rakefile
ADDED
data/fixy.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'fixy/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'fixy'
|
7
|
+
spec.version = Fixy::VERSION
|
8
|
+
spec.authors = ['Omar Skalli']
|
9
|
+
spec.email = ['omar@zenpayroll.com']
|
10
|
+
spec.description = %q{Library for generating fixed width flat files.}
|
11
|
+
spec.summary = %q{Provides a DSL for defining, generating, and debugging fixed width documents.}
|
12
|
+
spec.homepage = 'https://github.com/chetane/fixy'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_development_dependency 'bundler'
|
21
|
+
spec.add_development_dependency 'rspec'
|
22
|
+
|
23
|
+
spec.add_runtime_dependency 'rake'
|
24
|
+
spec.add_runtime_dependency 'activesupport'
|
25
|
+
end
|
data/lib/fixy.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Fixy
|
2
|
+
module Decorator
|
3
|
+
class Debug
|
4
|
+
class << self
|
5
|
+
def document(document)
|
6
|
+
'<html>
|
7
|
+
<head>
|
8
|
+
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
|
9
|
+
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
|
10
|
+
<style>
|
11
|
+
body { margin: 0; padding: 0; background-color: #CDCDCD; }
|
12
|
+
pre { margin: 0; }
|
13
|
+
.even { background-color: #ABABAB; }
|
14
|
+
.odd { background-color: #CDCDCD; }
|
15
|
+
b { font-size: 15px; }
|
16
|
+
.line { width: 50px; text-align: right; margin-right: 10px; color: #7f8284; display: inline-block; background-color: #272822; padding-right: 5px;}
|
17
|
+
span:hover { background-color: yellow; }
|
18
|
+
span.line:hover { background-color: #3e3d32; }
|
19
|
+
.tooltip { min-width: 250px; position: absolute; z-index: 1030; display: block; font-size: 12px; line-height: 1.4; visibility: visible; filter: alpha(opacity=0); opacity: 0; }
|
20
|
+
.tooltip.in { filter: alpha(opacity=90); opacity: .9; }
|
21
|
+
.tooltip.bottom { padding: 5px 0; margin-top: 3px; }
|
22
|
+
.tooltip-inner { max-width: 200px; padding: 3px 8px; color: #fff; text-align: center; text-decoration: none; background-color: #000; border-radius: 4px; }
|
23
|
+
.tooltip-arrow { position: absolute; width: 0; height: 0; border-color: transparent; border-style: solid; }
|
24
|
+
.tooltip.bottom .tooltip-arrow { top: 0; left: 50%; margin-left: -5px; border-width: 0 5px 5px; border-bottom-color: #000; }
|
25
|
+
</style>
|
26
|
+
<script type="text/javascript">
|
27
|
+
$(document).ready(function() {
|
28
|
+
$("div").each(function(i, div) {
|
29
|
+
$div = $(div);
|
30
|
+
$spans = $div.find("span");
|
31
|
+
$spans.each(function(j, span) {
|
32
|
+
element = $(span);
|
33
|
+
method = element.data("method");
|
34
|
+
size = element.data("size");
|
35
|
+
format = element.data("format");
|
36
|
+
column = element.data("column");
|
37
|
+
line = i + 1;
|
38
|
+
|
39
|
+
$(element).tooltip({
|
40
|
+
title: ("<b>" + method + "</b><br/>Line: " + line + "<br/>Column: " + column + "<br/>Length: " + size + "<br/>Formatter: " + format),
|
41
|
+
placement: "bottom",
|
42
|
+
container: "body",
|
43
|
+
html: true
|
44
|
+
});
|
45
|
+
});
|
46
|
+
$div.find("pre").prepend("<span class=\'line\'>" + (i + 1) + "</span>");
|
47
|
+
});
|
48
|
+
});
|
49
|
+
</script>
|
50
|
+
</head>
|
51
|
+
<body>' + document + '</body>
|
52
|
+
</html>'
|
53
|
+
end
|
54
|
+
|
55
|
+
def field(value, record_number, position, method, length, type)
|
56
|
+
"<span class='#{(record_number.even?? 'even' : 'odd')}' data-column='#{position}' data-method='#{method}' data-size='#{length}' data-format='#{type}'>#{value}</span>"
|
57
|
+
end
|
58
|
+
|
59
|
+
def record(record)
|
60
|
+
"<div><pre>#{record}</pre></div>"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Fixy
|
2
|
+
module Decorator
|
3
|
+
class Default
|
4
|
+
class << self
|
5
|
+
def document(document)
|
6
|
+
document
|
7
|
+
end
|
8
|
+
|
9
|
+
def field(value, record_number, position, method, length, type)
|
10
|
+
value
|
11
|
+
end
|
12
|
+
|
13
|
+
def record(record)
|
14
|
+
record
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Fixy
|
2
|
+
class Document
|
3
|
+
|
4
|
+
attr_accessor :content, :debug_mode
|
5
|
+
|
6
|
+
def generate_to_file(path, debug = false)
|
7
|
+
File.open(path, 'w') do |file|
|
8
|
+
file.write(generate(debug))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate(debug = false)
|
13
|
+
@debug_mode = debug
|
14
|
+
@content = ''
|
15
|
+
|
16
|
+
# Generate document based on user logic.
|
17
|
+
build
|
18
|
+
|
19
|
+
decorator.document(@content)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
def decorator
|
29
|
+
debug_mode ? Fixy::Decorator::Debug : Fixy::Decorator::Default
|
30
|
+
end
|
31
|
+
|
32
|
+
def prepend_record(record)
|
33
|
+
@content = record.generate(debug_mode) << @content
|
34
|
+
end
|
35
|
+
|
36
|
+
def append_record(record)
|
37
|
+
@content << record.generate(debug_mode)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Fixy
|
2
|
+
module Formatter
|
3
|
+
module Alphanumeric
|
4
|
+
|
5
|
+
#
|
6
|
+
# Alphanumeric Formatter
|
7
|
+
#
|
8
|
+
# Only contains printable characters and is
|
9
|
+
# left-justified and filled with spaces.
|
10
|
+
#
|
11
|
+
|
12
|
+
def format_alphanumeric(input, bytes)
|
13
|
+
input_string = String.new(input.to_s)
|
14
|
+
truncated_bytesize = [input_string.bytesize, bytes].min
|
15
|
+
if truncated_bytesize < bytes
|
16
|
+
input_string << " " * (bytes - truncated_bytesize)
|
17
|
+
input_string
|
18
|
+
else
|
19
|
+
input_string.byteslice(0..(truncated_bytesize - 1))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/fixy/record.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
module Fixy
|
2
|
+
class Record
|
3
|
+
require 'active_support/core_ext/proc'
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def set_record_length(count)
|
7
|
+
define_singleton_method('record_length') { count }
|
8
|
+
end
|
9
|
+
|
10
|
+
def field(name, size, range, type)
|
11
|
+
@record_fields ||= {}
|
12
|
+
range_matches = range.match /^(\d+)(?:-(\d+))?$/
|
13
|
+
|
14
|
+
# Make sure inputs are valid, we rather fail early than behave unexpectedly later.
|
15
|
+
raise ArgumentError, "Name '#{name}' is not a symbol" unless name.is_a? Symbol
|
16
|
+
raise ArgumentError, "Size '#{size}' is not a numeric" unless size.is_a?(Numeric) && size > 0
|
17
|
+
raise ArgumentError, "Range '#{range}' is invalid" unless range_matches
|
18
|
+
raise ArgumentError, "Unknown type '#{type}'" unless (private_instance_methods + instance_methods).include? "format_#{type}".to_sym
|
19
|
+
|
20
|
+
# Validate the range is consistent with size
|
21
|
+
range_from = Integer(range_matches[1])
|
22
|
+
range_to = Integer(range_matches[2].nil? ? range_matches[1] : range_matches[2])
|
23
|
+
valid_range = (range_from + (size - 1) == range_to)
|
24
|
+
|
25
|
+
raise ArgumentError, "Invalid Range (size: #{size}, range: #{range})" unless valid_range
|
26
|
+
raise ArgumentError, "Invalid Range (> #{record_length})" unless range_to <= record_length
|
27
|
+
|
28
|
+
# Ensure range is not already covered by another definition
|
29
|
+
(1..range_to).each do |column|
|
30
|
+
if @record_fields[column] && @record_fields[column][:to] >= range_from
|
31
|
+
raise ArgumentError, "Column #{column} has already been allocated"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# We're good to go :)
|
36
|
+
@record_fields[range_from] = { name: name, from: range_from, to: range_to, size: size, type: type}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Convenience method for creating field methods
|
40
|
+
def field_value(name, value)
|
41
|
+
|
42
|
+
# Make sure we're not overriding an existing method
|
43
|
+
if (private_instance_methods + instance_methods).include?(name)
|
44
|
+
raise ArgumentError, "Method '#{name}' is already defined, watch out for conflicts."
|
45
|
+
end
|
46
|
+
|
47
|
+
if value.is_a? Proc
|
48
|
+
define_method(name) { value.bind(self).call }
|
49
|
+
else
|
50
|
+
define_method(name) { value }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def record_fields
|
55
|
+
@record_fields
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_accessor :debug_mode
|
60
|
+
|
61
|
+
# Generate the entry based on the record structure
|
62
|
+
def generate(debug = false)
|
63
|
+
@debug_mode = debug
|
64
|
+
output = ''
|
65
|
+
current_position = 1
|
66
|
+
current_record = 1
|
67
|
+
|
68
|
+
while current_position <= self.class.record_length do
|
69
|
+
|
70
|
+
field = record_fields[current_position]
|
71
|
+
raise StandardError, "Undefined field for position #{current_position}" unless field
|
72
|
+
|
73
|
+
# We will first retrieve the value, then format it
|
74
|
+
method = field[:name]
|
75
|
+
value = send(method)
|
76
|
+
formatted_value = format_value(value, field[:size], field[:type])
|
77
|
+
formatted_value = decorator.field(formatted_value, current_record, current_position, method, field[:size], field[:type])
|
78
|
+
|
79
|
+
output << formatted_value
|
80
|
+
current_position = field[:to] + 1
|
81
|
+
current_record += 1
|
82
|
+
end
|
83
|
+
|
84
|
+
# Documentation mandates that every record ends with new line.
|
85
|
+
output << "\n"
|
86
|
+
|
87
|
+
# All ready. In the words of Mr. Peters: "Take it and go!"
|
88
|
+
decorator.record(output)
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Format value with user defined formatters.
|
94
|
+
def format_value(value, size, type)
|
95
|
+
send("format_#{type}".to_sym, value, size)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Retrieves the list of record fields that were set through the class methods.
|
99
|
+
def record_fields
|
100
|
+
self.class.record_fields
|
101
|
+
end
|
102
|
+
|
103
|
+
def decorator
|
104
|
+
debug_mode ? Fixy::Decorator::Debug : Fixy::Decorator::Default
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/fixy/version.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
|
4
|
+
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
|
5
|
+
<style>
|
6
|
+
body { margin: 0; padding: 0; background-color: #CDCDCD; }
|
7
|
+
pre { margin: 0; }
|
8
|
+
.even { background-color: #ABABAB; }
|
9
|
+
.odd { background-color: #CDCDCD; }
|
10
|
+
b { font-size: 15px; }
|
11
|
+
.line { width: 50px; text-align: right; margin-right: 10px; color: #7f8284; display: inline-block; background-color: #272822; padding-right: 5px;}
|
12
|
+
span:hover { background-color: yellow; }
|
13
|
+
span.line:hover { background-color: #3e3d32; }
|
14
|
+
.tooltip { min-width: 250px; position: absolute; z-index: 1030; display: block; font-size: 12px; line-height: 1.4; visibility: visible; filter: alpha(opacity=0); opacity: 0; }
|
15
|
+
.tooltip.in { filter: alpha(opacity=90); opacity: .9; }
|
16
|
+
.tooltip.bottom { padding: 5px 0; margin-top: 3px; }
|
17
|
+
.tooltip-inner { max-width: 200px; padding: 3px 8px; color: #fff; text-align: center; text-decoration: none; background-color: #000; border-radius: 4px; }
|
18
|
+
.tooltip-arrow { position: absolute; width: 0; height: 0; border-color: transparent; border-style: solid; }
|
19
|
+
.tooltip.bottom .tooltip-arrow { top: 0; left: 50%; margin-left: -5px; border-width: 0 5px 5px; border-bottom-color: #000; }
|
20
|
+
</style>
|
21
|
+
<script type="text/javascript">
|
22
|
+
$(document).ready(function() {
|
23
|
+
$("div").each(function(i, div) {
|
24
|
+
$div = $(div);
|
25
|
+
$spans = $div.find("span");
|
26
|
+
$spans.each(function(j, span) {
|
27
|
+
element = $(span);
|
28
|
+
method = element.data("method");
|
29
|
+
size = element.data("size");
|
30
|
+
format = element.data("format");
|
31
|
+
column = element.data("column");
|
32
|
+
line = i + 1;
|
33
|
+
|
34
|
+
$(element).tooltip({
|
35
|
+
title: ("<b>" + method + "</b><br/>Line: " + line + "<br/>Column: " + column + "<br/>Length: " + size + "<br/>Formatter: " + format),
|
36
|
+
placement: "bottom",
|
37
|
+
container: "body",
|
38
|
+
html: true
|
39
|
+
});
|
40
|
+
});
|
41
|
+
$div.find("pre").prepend("<span class='line'>" + (i + 1) + "</span>");
|
42
|
+
});
|
43
|
+
});
|
44
|
+
</script>
|
45
|
+
</head>
|
46
|
+
<body><div><pre><span class='odd' data-column='1' data-method='first_name' data-size='10' data-format='alphanumeric'>Arcturus </span><span class='even' data-column='11' data-method='last_name' data-size='10' data-format='alphanumeric'>Mengsk </span>
|
47
|
+
</pre></div><div><pre><span class='odd' data-column='1' data-method='first_name' data-size='10' data-format='alphanumeric'>Sarah </span><span class='even' data-column='11' data-method='last_name' data-size='10' data-format='alphanumeric'>Kerrigan </span>
|
48
|
+
</pre></div><div><pre><span class='odd' data-column='1' data-method='first_name' data-size='10' data-format='alphanumeric'>Jim </span><span class='even' data-column='11' data-method='last_name' data-size='10' data-format='alphanumeric'>Raynor </span>
|
49
|
+
</pre></div></body>
|
50
|
+
</html>
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Defining a Document' do
|
4
|
+
context 'when a build action is not defined' do
|
5
|
+
it 'should raise an exception' do
|
6
|
+
expect {
|
7
|
+
Fixy::Document.new.generate
|
8
|
+
}.to raise_error(NotImplementedError)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'when a build action is defined' do
|
13
|
+
it 'should generate fixed width document' do
|
14
|
+
|
15
|
+
class IdentityRecord < Fixy::Record
|
16
|
+
set_record_length 20
|
17
|
+
include Fixy::Formatter::Alphanumeric
|
18
|
+
field :first_name, 10, '1-10' , :alphanumeric
|
19
|
+
field :last_name , 10, '11-20', :alphanumeric
|
20
|
+
|
21
|
+
def initialize(first_name, last_name)
|
22
|
+
@first_name = first_name
|
23
|
+
@last_name = last_name
|
24
|
+
end
|
25
|
+
|
26
|
+
field_value :first_name, -> { @first_name }
|
27
|
+
field_value :last_name , -> { @last_name }
|
28
|
+
end
|
29
|
+
|
30
|
+
class PeopleDocument < Fixy::Document
|
31
|
+
def build
|
32
|
+
append_record IdentityRecord.new('Sarah', 'Kerrigan')
|
33
|
+
append_record IdentityRecord.new('Jim', 'Raynor')
|
34
|
+
prepend_record IdentityRecord.new('Arcturus', 'Mengsk')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
PeopleDocument.new.generate "Arcturus Mengsk \nSarah Kerrigan \nJim Raynor "
|
39
|
+
PeopleDocument.new.generate(true).should eq File.read('spec/fixtures/debug_document.txt')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Defining a Record' do
|
4
|
+
context 'when the definition is correct' do
|
5
|
+
it 'should not raise any exception' do
|
6
|
+
expect {
|
7
|
+
class PersonRecord < Fixy::Record
|
8
|
+
include Fixy::Formatter::Alphanumeric
|
9
|
+
|
10
|
+
set_record_length 20
|
11
|
+
|
12
|
+
field :first_name, 10, '1-10' , :alphanumeric
|
13
|
+
field :last_name , 10, '11-20', :alphanumeric
|
14
|
+
end
|
15
|
+
}.not_to raise_error
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when the definition is incorrect' do
|
20
|
+
it 'should raise appropriate exception' do
|
21
|
+
expect {
|
22
|
+
class PersonRecordA < Fixy::Record
|
23
|
+
set_record_length 20
|
24
|
+
field :first_name, 10, '1-10', :alphanumeric
|
25
|
+
end
|
26
|
+
}.to raise_error(ArgumentError, "Unknown type 'alphanumeric'")
|
27
|
+
|
28
|
+
expect {
|
29
|
+
class PersonRecordB < Fixy::Record
|
30
|
+
include Fixy::Formatter::Alphanumeric
|
31
|
+
set_record_length 20
|
32
|
+
field :first_name, 2, '1-10', :alphanumeric
|
33
|
+
end
|
34
|
+
}.to raise_error(ArgumentError, "Invalid Range (size: 2, range: 1-10)")
|
35
|
+
|
36
|
+
expect {
|
37
|
+
class PersonRecordC < Fixy::Record
|
38
|
+
include Fixy::Formatter::Alphanumeric
|
39
|
+
set_record_length 20
|
40
|
+
field :first_name, 10, '1-10', :alphanumeric
|
41
|
+
field :last_name , 10, '10-19', :alphanumeric
|
42
|
+
end
|
43
|
+
}.to raise_error(ArgumentError, "Column 1 has already been allocated")
|
44
|
+
|
45
|
+
expect {
|
46
|
+
class PersonRecordD < Fixy::Record
|
47
|
+
include Fixy::Formatter::Alphanumeric
|
48
|
+
set_record_length 10
|
49
|
+
field :first_name, 10, '1-10', :alphanumeric
|
50
|
+
field :last_name , 10, '11-20', :alphanumeric
|
51
|
+
end
|
52
|
+
}.to raise_error(ArgumentError, "Invalid Range (> 10)")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'Generating a Record' do
|
58
|
+
context 'when properly defined' do
|
59
|
+
it 'should generate fixed width record' do
|
60
|
+
class PersonRecordE < Fixy::Record
|
61
|
+
include Fixy::Formatter::Alphanumeric
|
62
|
+
|
63
|
+
set_record_length 20
|
64
|
+
|
65
|
+
field :first_name, 10, '1-10' , :alphanumeric
|
66
|
+
field :last_name , 10, '11-20', :alphanumeric
|
67
|
+
|
68
|
+
field_value :first_name, -> { 'Sarah' }
|
69
|
+
|
70
|
+
def last_name
|
71
|
+
'Kerrigan'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
PersonRecordE.new.generate.should eq "Sarah Kerrigan \n"
|
76
|
+
PersonRecordE.new.generate(true).should eq File.read('spec/fixtures/debug_record.txt')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'when definition is incomplete (e.g. undefined columns)' do
|
81
|
+
it 'should raise an error' do
|
82
|
+
class PersonRecordF < Fixy::Record
|
83
|
+
include Fixy::Formatter::Alphanumeric
|
84
|
+
set_record_length 20
|
85
|
+
field :first_name, 10, '1-10' , :alphanumeric
|
86
|
+
field :last_name , 8, '11-18', :alphanumeric
|
87
|
+
field_value :first_name, -> { 'Sarah' }
|
88
|
+
field_value :last_name, -> { 'Kerrigan' }
|
89
|
+
end
|
90
|
+
|
91
|
+
expect {
|
92
|
+
PersonRecordF.new.generate
|
93
|
+
}.to raise_error(StandardError, "Undefined field for position 19")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fixy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Omar Skalli
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-05-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: activesupport
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Library for generating fixed width flat files.
|
79
|
+
email:
|
80
|
+
- omar@zenpayroll.com
|
81
|
+
executables: []
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files: []
|
84
|
+
files:
|
85
|
+
- .gitignore
|
86
|
+
- CHANGELOG.md
|
87
|
+
- Gemfile
|
88
|
+
- LICENSE.txt
|
89
|
+
- README.md
|
90
|
+
- Rakefile
|
91
|
+
- fixy.gemspec
|
92
|
+
- lib/fixy.rb
|
93
|
+
- lib/fixy/decorator/debug.rb
|
94
|
+
- lib/fixy/decorator/default.rb
|
95
|
+
- lib/fixy/document.rb
|
96
|
+
- lib/fixy/formatter/alphanumeric.rb
|
97
|
+
- lib/fixy/record.rb
|
98
|
+
- lib/fixy/version.rb
|
99
|
+
- spec/fixtures/debug_document.txt
|
100
|
+
- spec/fixtures/debug_record.txt
|
101
|
+
- spec/fixy/document_spec.rb
|
102
|
+
- spec/fixy/record_spec.rb
|
103
|
+
- spec/spec_helper.rb
|
104
|
+
homepage: https://github.com/chetane/fixy
|
105
|
+
licenses:
|
106
|
+
- MIT
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ! '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ! '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
requirements: []
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 1.8.24
|
126
|
+
signing_key:
|
127
|
+
specification_version: 3
|
128
|
+
summary: Provides a DSL for defining, generating, and debugging fixed width documents.
|
129
|
+
test_files:
|
130
|
+
- spec/fixtures/debug_document.txt
|
131
|
+
- spec/fixtures/debug_record.txt
|
132
|
+
- spec/fixy/document_spec.rb
|
133
|
+
- spec/fixy/record_spec.rb
|
134
|
+
- spec/spec_helper.rb
|