fixed-layout-mapper 0.0.2 → 0.0.3
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.
- checksums.yaml +7 -0
- data/.gitignore +5 -4
- data/Gemfile +4 -4
- data/README.md +143 -138
- data/Rakefile +8 -8
- data/fixed-layout-mapper.gemspec +25 -24
- data/lib/fixed-layout-mapper.rb +250 -250
- data/lib/fixed-layout-mapper/version.rb +3 -3
- data/test/fixed_layout_mapper_spec.rb +241 -241
- metadata +39 -12
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 49c68dd5dff9b06434fecdd63f60ba65deaa2fd4
|
4
|
+
data.tar.gz: bccad641631a17230d769ba03eefa6cc282fdba2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3eafe44a058e6e34730b4dfd59ea7f2c0f5a0fa2e67d3b9d79d534e6fecd0aa61759fa158a03566569074124bf88f1da04adf0089d6c739aff09ba96da1e753a
|
7
|
+
data.tar.gz: dc2275ebc8ef3dfb0940d89c029307c036468a9dc01a89d7f580908bc196c2ecfdb035cacf8946bb2c8effa5e9ef40824b4c1d0940a974debdb92eaada01fe74
|
data/.gitignore
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
*.gem
|
2
|
-
.bundle
|
3
|
-
Gemfile.lock
|
4
|
-
pkg/*
|
1
|
+
*.gem
|
2
|
+
.bundle
|
3
|
+
Gemfile.lock
|
4
|
+
pkg/*
|
5
|
+
vendor
|
data/Gemfile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
source "http://rubygems.org"
|
2
|
-
|
3
|
-
# Specify your gem's dependencies in fixed-layout-mapper.gemspec
|
4
|
-
gemspec
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in fixed-layout-mapper.gemspec
|
4
|
+
gemspec
|
data/README.md
CHANGED
@@ -1,138 +1,143 @@
|
|
1
|
-
|
2
|
-
====
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
mapper
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
1
|
+
Description
|
2
|
+
====
|
3
|
+
|
4
|
+
fixed_layout_mapper is a library that maps to RubyObject from fixed-length data.
|
5
|
+
|
6
|
+
Sample
|
7
|
+
====
|
8
|
+
require 'pp'
|
9
|
+
require 'fixed-layout-mapper'
|
10
|
+
|
11
|
+
mapper = FixedLayoutMapper::Mapper.define_layout do
|
12
|
+
layout :sub_layout do
|
13
|
+
col :sub_f1, 1
|
14
|
+
col :sub_f2, 2
|
15
|
+
end
|
16
|
+
|
17
|
+
col :f1, 5
|
18
|
+
col :f2, :sub_layout
|
19
|
+
col :f3, [2] * 3
|
20
|
+
col :f4, [1, 2, :sub_layout]
|
21
|
+
col :f5, 3 do |v|
|
22
|
+
v * 2
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
data = %w(12345 a bb 00 11 22 a bb c dd 123)
|
27
|
+
pp mapper.map(data.join)
|
28
|
+
#=> #<struct
|
29
|
+
# f1="12345",
|
30
|
+
# f2=#<struct sub_f1="a", sub_f2="bb">,
|
31
|
+
# f3=["00", "11", "22"],
|
32
|
+
# f4=["a", "bb", #<struct sub_f1="c", sub_f2="dd">],
|
33
|
+
# f5="123123">
|
34
|
+
|
35
|
+
Detail
|
36
|
+
====
|
37
|
+
|
38
|
+
Define columns
|
39
|
+
----
|
40
|
+
Layout definition is started in "define_layout" method.
|
41
|
+
|
42
|
+
mapper = FixedLayoutMapper::Mapper.define_layout do
|
43
|
+
definitions...
|
44
|
+
end
|
45
|
+
|
46
|
+
Columns are defined by "col" method.
|
47
|
+
|
48
|
+
col symbol, record_def
|
49
|
+
|
50
|
+
record_def is
|
51
|
+
* numeric: define column which cut the string with its width.
|
52
|
+
|
53
|
+
#ex)
|
54
|
+
mapper = FixedLayoutMapper::Mapper.define_layout do
|
55
|
+
col :f, 3
|
56
|
+
end
|
57
|
+
|
58
|
+
p mapper.map("0123")
|
59
|
+
#=> #<struct f="012">
|
60
|
+
|
61
|
+
* array: define array column with use its each contents.
|
62
|
+
|
63
|
+
#ex)
|
64
|
+
mapper = FixedLayoutMapper::Mapper.define_layout do
|
65
|
+
col :f1, [1, 2]
|
66
|
+
col :f2, [1] * 3
|
67
|
+
end
|
68
|
+
|
69
|
+
p mapper.map("001123")
|
70
|
+
#=> #<struct f1=["0", "01"], f2=["1", "2", "3"]>
|
71
|
+
|
72
|
+
* sub_layout_key: define column which use its layout.
|
73
|
+
see Sub layout.
|
74
|
+
|
75
|
+
Sub layout
|
76
|
+
----
|
77
|
+
|
78
|
+
if you want to name to layout, you can use "layout" method.
|
79
|
+
A field defined by "col" method called outside the block of "layout" method is implicitly defined in the default layout.
|
80
|
+
|
81
|
+
layout layout_name(symbol) do
|
82
|
+
column definitions...
|
83
|
+
end
|
84
|
+
|
85
|
+
When sub layout is defined, you can use its layout in other layout definition.
|
86
|
+
|
87
|
+
mapper = FixedLayoutMapper::Mapper.define_layout do
|
88
|
+
layout :sub1 do
|
89
|
+
col :sub_f1, 1
|
90
|
+
col :sub_f2, 2
|
91
|
+
end
|
92
|
+
col :f1, 1
|
93
|
+
col :f2, :sub1
|
94
|
+
end
|
95
|
+
|
96
|
+
p mapper.map("0123")
|
97
|
+
#=> #<struct f1="0", f2=#<struct sub_f1="1", sub_f2="23">>
|
98
|
+
|
99
|
+
You can also use sub layout in array def.
|
100
|
+
|
101
|
+
mapper = FixedLayoutMapper::Mapper.define_layout do
|
102
|
+
layout :sub1 do
|
103
|
+
col :sub_f1, 1
|
104
|
+
col :sub_f2, 2
|
105
|
+
end
|
106
|
+
col :f1, [1, :sub1]
|
107
|
+
end
|
108
|
+
|
109
|
+
p mapper.map("0123")
|
110
|
+
#=> #<struct f1=["0", #<struct sub_f1="1", sub_f2="23"]>
|
111
|
+
|
112
|
+
you can use sub layout in map method.
|
113
|
+
if symbol is passed to the second argument in map method, use its layout.
|
114
|
+
|
115
|
+
mapper = FixedLayoutMapper::Mapper.define_layout do
|
116
|
+
layout :sub1 do
|
117
|
+
col :sub_f1, 1
|
118
|
+
col :sub_f2, 2
|
119
|
+
end
|
120
|
+
col :f1, [1, :sub1]
|
121
|
+
end
|
122
|
+
|
123
|
+
p mapper.map("0123")
|
124
|
+
#=> #<struct f1=["0", #<struct sub_f1="1", sub_f2="23"]>
|
125
|
+
p mapper.map("0123", :sub1)
|
126
|
+
#=> #<struct sub_f1="0", sub_f2="12">
|
127
|
+
|
128
|
+
|
129
|
+
Conversion
|
130
|
+
----
|
131
|
+
|
132
|
+
If "col" method is given block, the raw value is passed to the block and
|
133
|
+
the field value becomes the return value of the block.
|
134
|
+
|
135
|
+
mapper = FixedLayoutMapper::Mapper.define_layout do
|
136
|
+
col :f1, 3 do |v|
|
137
|
+
v + "_" + v
|
138
|
+
end
|
139
|
+
col :f2, 3, &:upcase
|
140
|
+
end
|
141
|
+
|
142
|
+
p mapper.map("012abc")
|
143
|
+
#=> #<struct f1="012_012", f2="ABC">
|
data/Rakefile
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
2
|
-
|
3
|
-
require 'rspec/core'
|
4
|
-
require 'rspec/core/rake_task'
|
5
|
-
|
6
|
-
RSpec::Core::RakeTask.new(:spec) do |spec|
|
7
|
-
spec.pattern = FileList['test/**/*_spec.rb']
|
8
|
-
end
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'rspec/core'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
7
|
+
spec.pattern = FileList['test/**/*_spec.rb']
|
8
|
+
end
|
data/fixed-layout-mapper.gemspec
CHANGED
@@ -1,24 +1,25 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require "fixed-layout-mapper/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |s|
|
6
|
-
s.name = "fixed-layout-mapper"
|
7
|
-
s.version = FixedLayoutMapper::VERSION
|
8
|
-
s.authors = ["pocari"]
|
9
|
-
s.email = ["caffelattenonsugar@gmail.com"]
|
10
|
-
s.homepage = "https://github.com/pocari"
|
11
|
-
s.summary = %q{Mapping fixed layout record to ruby's Struct or Array object.}
|
12
|
-
s.description = ""
|
13
|
-
|
14
|
-
s.rubyforge_project = "fixed-layout-mapper"
|
15
|
-
|
16
|
-
s.files = `git ls-files`.split("\n")
|
17
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
-
s.require_paths = ["lib"]
|
20
|
-
|
21
|
-
# specify any dependencies here; for example:
|
22
|
-
|
23
|
-
|
24
|
-
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "fixed-layout-mapper/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "fixed-layout-mapper"
|
7
|
+
s.version = FixedLayoutMapper::VERSION
|
8
|
+
s.authors = ["pocari"]
|
9
|
+
s.email = ["caffelattenonsugar@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/pocari"
|
11
|
+
s.summary = %q{Mapping fixed layout record to ruby's Struct or Array object.}
|
12
|
+
s.description = ""
|
13
|
+
|
14
|
+
s.rubyforge_project = "fixed-layout-mapper"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
# s.add_runtime_dependency "rest-client"
|
25
|
+
end
|
data/lib/fixed-layout-mapper.rb
CHANGED
@@ -1,250 +1,250 @@
|
|
1
|
-
require "fixed-layout-mapper/version"
|
2
|
-
|
3
|
-
module FixedLayoutMapper
|
4
|
-
LENGTH_CONDITION_STRICT = :strict
|
5
|
-
LENGTH_CONDITION_ALLOW_LONG = :allow_long
|
6
|
-
|
7
|
-
class Mapper
|
8
|
-
class ColumnMapper
|
9
|
-
attr_accessor :layout_mapper, :converter
|
10
|
-
def initialize(layout_mapper, converter = nil)
|
11
|
-
@layout_mapper = layout_mapper
|
12
|
-
@converter = converter
|
13
|
-
end
|
14
|
-
|
15
|
-
def convert(value)
|
16
|
-
if @converter
|
17
|
-
@converter.(value)
|
18
|
-
else
|
19
|
-
value
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class SimpleMapper < ColumnMapper
|
25
|
-
def initialize(layout_mapper, len, converter = nil)
|
26
|
-
super layout_mapper, converter
|
27
|
-
@len = len
|
28
|
-
end
|
29
|
-
|
30
|
-
def length
|
31
|
-
@len
|
32
|
-
end
|
33
|
-
|
34
|
-
def map(bytes)
|
35
|
-
value = bytes.take(@len).pack("C*").force_encoding(layout_mapper.encoding)
|
36
|
-
#value = converter.(value) if converter
|
37
|
-
[convert(value), bytes.drop(@len)]
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
class SubLayoutMapper < ColumnMapper
|
42
|
-
def initialize(layout_mapper, layout_id, converter = nil)
|
43
|
-
super layout_mapper, converter
|
44
|
-
@layout_id = layout_id
|
45
|
-
end
|
46
|
-
|
47
|
-
def map(bytes)
|
48
|
-
value, rest = layout_mapper.get_layout(@layout_id).map(bytes, layout_mapper.get_result_class(@layout_id))
|
49
|
-
[convert(value), rest]
|
50
|
-
end
|
51
|
-
|
52
|
-
def length
|
53
|
-
layout_mapper.get_layout(@layout_id).length
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
class ArrayMapper < ColumnMapper
|
58
|
-
def initialize(layout_mapper, mappers, converter = nil)
|
59
|
-
super layout_mapper, converter
|
60
|
-
@mappers = mappers
|
61
|
-
end
|
62
|
-
|
63
|
-
def map(bytes)
|
64
|
-
ret = []
|
65
|
-
rest = @mappers.inject(bytes) do |acc, mapper|
|
66
|
-
value, acc = mapper.map(acc)
|
67
|
-
ret << value
|
68
|
-
acc
|
69
|
-
end
|
70
|
-
[convert(ret), rest]
|
71
|
-
end
|
72
|
-
|
73
|
-
def length
|
74
|
-
@mappers.inject(0) do |acc, m|
|
75
|
-
acc += m.length
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
class Layout
|
81
|
-
def initialize(length_condition)
|
82
|
-
@length_condition = length_condition
|
83
|
-
@layout = []
|
84
|
-
@length = nil
|
85
|
-
end
|
86
|
-
|
87
|
-
def syms
|
88
|
-
@layout.map{|e| e[0]}
|
89
|
-
end
|
90
|
-
|
91
|
-
def add(sym, mapper)
|
92
|
-
@layout << [sym, mapper]
|
93
|
-
end
|
94
|
-
|
95
|
-
def length
|
96
|
-
@length ||= calc_length
|
97
|
-
end
|
98
|
-
|
99
|
-
def calc_length
|
100
|
-
@layout.inject(0) do |acc, (sym, mapper)|
|
101
|
-
acc += mapper.length
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def map(bytes, result_class)
|
106
|
-
case @length_condition
|
107
|
-
when LENGTH_CONDITION_STRICT
|
108
|
-
raise "byte length is invalid" unless bytes.length == length
|
109
|
-
when LENGTH_CONDITION_ALLOW_LONG
|
110
|
-
raise "byte length is too short" if bytes.length < length
|
111
|
-
else
|
112
|
-
raise "unknown LENGTH_CONDIGION #{@length_condition}"
|
113
|
-
end
|
114
|
-
|
115
|
-
obj = result_class.new
|
116
|
-
rest = @layout.inject(bytes) do |acc, (sym, mapper)|
|
117
|
-
value, acc = mapper.map(acc)
|
118
|
-
obj[sym] = value
|
119
|
-
acc
|
120
|
-
end
|
121
|
-
[obj, rest]
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
class << self
|
126
|
-
def define_layout(opts = {:encoding => Encoding.default_external}, &block)
|
127
|
-
obj = Mapper.new(opts)
|
128
|
-
obj.define_layout(&block)
|
129
|
-
obj
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
attr_reader :encoding, :length_condition
|
134
|
-
def initialize(opts = {:encoding => Encoding.default_external})
|
135
|
-
@current_layout = :default_layout
|
136
|
-
@current_length_condition = LENGTH_CONDITION_ALLOW_LONG
|
137
|
-
@layouts = {}
|
138
|
-
@result_class = {}
|
139
|
-
@length_conditions = {}
|
140
|
-
@encoding = opts[:encoding]
|
141
|
-
end
|
142
|
-
|
143
|
-
def define_layout(&block)
|
144
|
-
instance_eval(&block)
|
145
|
-
build_layout
|
146
|
-
end
|
147
|
-
|
148
|
-
def map(data, layout_id = @current_layout)
|
149
|
-
obj, = @layouts[layout_id].map(data.unpack("C*"), @result_class[layout_id])
|
150
|
-
obj
|
151
|
-
end
|
152
|
-
|
153
|
-
def get_layout(layout_id = @current_layout)
|
154
|
-
@layouts[layout_id]
|
155
|
-
end
|
156
|
-
|
157
|
-
def get_result_class(layout_id)
|
158
|
-
@result_class[layout_id]
|
159
|
-
end
|
160
|
-
|
161
|
-
def length_condigion(value)
|
162
|
-
@length_condition = value
|
163
|
-
end
|
164
|
-
|
165
|
-
private
|
166
|
-
def build_layout
|
167
|
-
@layouts.each do |layout_id, layout|
|
168
|
-
@result_class[layout_id] = Struct.new(*layout.syms) do
|
169
|
-
def to_hash
|
170
|
-
each_pair.inject({}) do |acc, (key, value)|
|
171
|
-
case
|
172
|
-
when value.respond_to?(:to_hash)
|
173
|
-
acc[key] = value.to_hash
|
174
|
-
when Array === value
|
175
|
-
acc[key] = value.map{|e|
|
176
|
-
if e.respond_to?(:to_hash)
|
177
|
-
e.to_hash
|
178
|
-
else
|
179
|
-
e
|
180
|
-
end
|
181
|
-
}
|
182
|
-
else
|
183
|
-
acc[key] = value
|
184
|
-
end
|
185
|
-
acc
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
def layout(layout_id, length_condition = LENGTH_CONDITION_ALLOW_LONG, &block)
|
193
|
-
change_current_layout(layout_id, length_condition) do
|
194
|
-
instance_eval(&block)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
def col(sym, map_info, layout_id = @current_layout, &block)
|
199
|
-
case map_info
|
200
|
-
when Numeric
|
201
|
-
col_len(sym, map_info, layout_id, block)
|
202
|
-
when Symbol
|
203
|
-
col_from_sub_layout(sym, map_info, layout_id, block)
|
204
|
-
when Array
|
205
|
-
col_array(sym, map_info, layout_id, block)
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
def create_layout
|
210
|
-
Layout.new(@current_length_condition)
|
211
|
-
end
|
212
|
-
|
213
|
-
def col_len(sym, len, layout_id = @current_layout, block)
|
214
|
-
@layouts[layout_id] ||= create_layout
|
215
|
-
@layouts[layout_id].add(sym, SimpleMapper.new(self, len, block))
|
216
|
-
end
|
217
|
-
|
218
|
-
def col_from_sub_layout(sym, sub_layout, layout_id = @current_layout, block)
|
219
|
-
@layouts[layout_id] ||= create_layout
|
220
|
-
@layouts[layout_id].add(sym, SubLayoutMapper.new(self, sub_layout, block))
|
221
|
-
end
|
222
|
-
|
223
|
-
def col_array(sym, array_param, layout_id = @current_layout, block)
|
224
|
-
@layouts[layout_id] ||= create_layout
|
225
|
-
@layouts[layout_id].add(sym, ArrayMapper.new(self,
|
226
|
-
array_param.map{|arg|
|
227
|
-
case arg
|
228
|
-
when Numeric
|
229
|
-
mapper_class = SimpleMapper
|
230
|
-
when Symbol
|
231
|
-
mapper_class = SubLayoutMapper
|
232
|
-
else
|
233
|
-
raise "elements must be Numeric or Symbol"
|
234
|
-
end
|
235
|
-
mapper_class.new(self, arg)
|
236
|
-
}, block)
|
237
|
-
)
|
238
|
-
end
|
239
|
-
|
240
|
-
def change_current_layout(layout, length_condition)
|
241
|
-
tmp = @current_layout
|
242
|
-
tmp = @current_length_condition
|
243
|
-
@current_layout = layout
|
244
|
-
@current_length_condition = length_condition
|
245
|
-
yield
|
246
|
-
@current_layout = tmp
|
247
|
-
@current_length_condition = tmp
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
1
|
+
require "fixed-layout-mapper/version"
|
2
|
+
|
3
|
+
module FixedLayoutMapper
|
4
|
+
LENGTH_CONDITION_STRICT = :strict
|
5
|
+
LENGTH_CONDITION_ALLOW_LONG = :allow_long
|
6
|
+
|
7
|
+
class Mapper
|
8
|
+
class ColumnMapper
|
9
|
+
attr_accessor :layout_mapper, :converter
|
10
|
+
def initialize(layout_mapper, converter = nil)
|
11
|
+
@layout_mapper = layout_mapper
|
12
|
+
@converter = converter
|
13
|
+
end
|
14
|
+
|
15
|
+
def convert(value)
|
16
|
+
if @converter
|
17
|
+
@converter.(value)
|
18
|
+
else
|
19
|
+
value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class SimpleMapper < ColumnMapper
|
25
|
+
def initialize(layout_mapper, len, converter = nil)
|
26
|
+
super layout_mapper, converter
|
27
|
+
@len = len
|
28
|
+
end
|
29
|
+
|
30
|
+
def length
|
31
|
+
@len
|
32
|
+
end
|
33
|
+
|
34
|
+
def map(bytes)
|
35
|
+
value = bytes.take(@len).pack("C*").force_encoding(layout_mapper.encoding)
|
36
|
+
#value = converter.(value) if converter
|
37
|
+
[convert(value), bytes.drop(@len)]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class SubLayoutMapper < ColumnMapper
|
42
|
+
def initialize(layout_mapper, layout_id, converter = nil)
|
43
|
+
super layout_mapper, converter
|
44
|
+
@layout_id = layout_id
|
45
|
+
end
|
46
|
+
|
47
|
+
def map(bytes)
|
48
|
+
value, rest = layout_mapper.get_layout(@layout_id).map(bytes, layout_mapper.get_result_class(@layout_id))
|
49
|
+
[convert(value), rest]
|
50
|
+
end
|
51
|
+
|
52
|
+
def length
|
53
|
+
layout_mapper.get_layout(@layout_id).length
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class ArrayMapper < ColumnMapper
|
58
|
+
def initialize(layout_mapper, mappers, converter = nil)
|
59
|
+
super layout_mapper, converter
|
60
|
+
@mappers = mappers
|
61
|
+
end
|
62
|
+
|
63
|
+
def map(bytes)
|
64
|
+
ret = []
|
65
|
+
rest = @mappers.inject(bytes) do |acc, mapper|
|
66
|
+
value, acc = mapper.map(acc)
|
67
|
+
ret << value
|
68
|
+
acc
|
69
|
+
end
|
70
|
+
[convert(ret), rest]
|
71
|
+
end
|
72
|
+
|
73
|
+
def length
|
74
|
+
@mappers.inject(0) do |acc, m|
|
75
|
+
acc += m.length
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Layout
|
81
|
+
def initialize(length_condition)
|
82
|
+
@length_condition = length_condition
|
83
|
+
@layout = []
|
84
|
+
@length = nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def syms
|
88
|
+
@layout.map{|e| e[0]}
|
89
|
+
end
|
90
|
+
|
91
|
+
def add(sym, mapper)
|
92
|
+
@layout << [sym, mapper]
|
93
|
+
end
|
94
|
+
|
95
|
+
def length
|
96
|
+
@length ||= calc_length
|
97
|
+
end
|
98
|
+
|
99
|
+
def calc_length
|
100
|
+
@layout.inject(0) do |acc, (sym, mapper)|
|
101
|
+
acc += mapper.length
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def map(bytes, result_class)
|
106
|
+
case @length_condition
|
107
|
+
when LENGTH_CONDITION_STRICT
|
108
|
+
raise "byte length is invalid" unless bytes.length == length
|
109
|
+
when LENGTH_CONDITION_ALLOW_LONG
|
110
|
+
raise "byte length is too short" if bytes.length < length
|
111
|
+
else
|
112
|
+
raise "unknown LENGTH_CONDIGION #{@length_condition}"
|
113
|
+
end
|
114
|
+
|
115
|
+
obj = result_class.new
|
116
|
+
rest = @layout.inject(bytes) do |acc, (sym, mapper)|
|
117
|
+
value, acc = mapper.map(acc)
|
118
|
+
obj[sym] = value
|
119
|
+
acc
|
120
|
+
end
|
121
|
+
[obj, rest]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class << self
|
126
|
+
def define_layout(opts = {:encoding => Encoding.default_external}, &block)
|
127
|
+
obj = Mapper.new(opts)
|
128
|
+
obj.define_layout(&block)
|
129
|
+
obj
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
attr_reader :encoding, :length_condition
|
134
|
+
def initialize(opts = {:encoding => Encoding.default_external})
|
135
|
+
@current_layout = :default_layout
|
136
|
+
@current_length_condition = LENGTH_CONDITION_ALLOW_LONG
|
137
|
+
@layouts = {}
|
138
|
+
@result_class = {}
|
139
|
+
@length_conditions = {}
|
140
|
+
@encoding = opts[:encoding]
|
141
|
+
end
|
142
|
+
|
143
|
+
def define_layout(&block)
|
144
|
+
instance_eval(&block)
|
145
|
+
build_layout
|
146
|
+
end
|
147
|
+
|
148
|
+
def map(data, layout_id = @current_layout)
|
149
|
+
obj, = @layouts[layout_id].map(data.unpack("C*"), @result_class[layout_id])
|
150
|
+
obj
|
151
|
+
end
|
152
|
+
|
153
|
+
def get_layout(layout_id = @current_layout)
|
154
|
+
@layouts[layout_id]
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_result_class(layout_id)
|
158
|
+
@result_class[layout_id]
|
159
|
+
end
|
160
|
+
|
161
|
+
def length_condigion(value)
|
162
|
+
@length_condition = value
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
def build_layout
|
167
|
+
@layouts.each do |layout_id, layout|
|
168
|
+
@result_class[layout_id] = Struct.new(*layout.syms) do
|
169
|
+
def to_hash
|
170
|
+
each_pair.inject({}) do |acc, (key, value)|
|
171
|
+
case
|
172
|
+
when value.respond_to?(:to_hash)
|
173
|
+
acc[key] = value.to_hash
|
174
|
+
when Array === value
|
175
|
+
acc[key] = value.map{|e|
|
176
|
+
if e.respond_to?(:to_hash)
|
177
|
+
e.to_hash
|
178
|
+
else
|
179
|
+
e
|
180
|
+
end
|
181
|
+
}
|
182
|
+
else
|
183
|
+
acc[key] = value
|
184
|
+
end
|
185
|
+
acc
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def layout(layout_id, length_condition = LENGTH_CONDITION_ALLOW_LONG, &block)
|
193
|
+
change_current_layout(layout_id, length_condition) do
|
194
|
+
instance_eval(&block)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def col(sym, map_info, layout_id = @current_layout, &block)
|
199
|
+
case map_info
|
200
|
+
when Numeric
|
201
|
+
col_len(sym, map_info, layout_id, block)
|
202
|
+
when Symbol
|
203
|
+
col_from_sub_layout(sym, map_info, layout_id, block)
|
204
|
+
when Array
|
205
|
+
col_array(sym, map_info, layout_id, block)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def create_layout
|
210
|
+
Layout.new(@current_length_condition)
|
211
|
+
end
|
212
|
+
|
213
|
+
def col_len(sym, len, layout_id = @current_layout, block)
|
214
|
+
@layouts[layout_id] ||= create_layout
|
215
|
+
@layouts[layout_id].add(sym, SimpleMapper.new(self, len, block))
|
216
|
+
end
|
217
|
+
|
218
|
+
def col_from_sub_layout(sym, sub_layout, layout_id = @current_layout, block)
|
219
|
+
@layouts[layout_id] ||= create_layout
|
220
|
+
@layouts[layout_id].add(sym, SubLayoutMapper.new(self, sub_layout, block))
|
221
|
+
end
|
222
|
+
|
223
|
+
def col_array(sym, array_param, layout_id = @current_layout, block)
|
224
|
+
@layouts[layout_id] ||= create_layout
|
225
|
+
@layouts[layout_id].add(sym, ArrayMapper.new(self,
|
226
|
+
array_param.map{|arg|
|
227
|
+
case arg
|
228
|
+
when Numeric
|
229
|
+
mapper_class = SimpleMapper
|
230
|
+
when Symbol
|
231
|
+
mapper_class = SubLayoutMapper
|
232
|
+
else
|
233
|
+
raise "elements must be Numeric or Symbol"
|
234
|
+
end
|
235
|
+
mapper_class.new(self, arg)
|
236
|
+
}, block)
|
237
|
+
)
|
238
|
+
end
|
239
|
+
|
240
|
+
def change_current_layout(layout, length_condition)
|
241
|
+
tmp = @current_layout
|
242
|
+
tmp = @current_length_condition
|
243
|
+
@current_layout = layout
|
244
|
+
@current_length_condition = length_condition
|
245
|
+
yield
|
246
|
+
@current_layout = tmp
|
247
|
+
@current_length_condition = tmp
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|