packed_struct 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +11 -1
- data/lib/packed_struct.rb +23 -0
- data/lib/packed_struct/directive.rb +255 -272
- data/lib/packed_struct/modifier.rb +76 -0
- data/lib/packed_struct/package.rb +26 -38
- data/lib/packed_struct/version.rb +1 -1
- metadata +3 -2
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# PackedStruct
|
1
|
+
# PackedStruct [![Build Status](https://travis-ci.org/redjazz96/packed_struct.png?branch=master)](https://travis-ci.org/redjazz96/packed_struct)
|
2
2
|
|
3
3
|
`PackedStruct` is a way to define packing strings (see [`Array#pack`](http://ruby-doc.org/core-2.0/Array.html#method-i-pack)).
|
4
4
|
It was created after @charliesome suggested [a format](https://gist.github.com/redjazz96/6dda0554f62e4f77253a) for defining these strings, but never finished it.
|
@@ -10,6 +10,7 @@ class RconPacket
|
|
10
10
|
include PackedStruct
|
11
11
|
struct_layout :packet do
|
12
12
|
little_endian signed size[32] # defaults to a number of size 32.
|
13
|
+
# also the same as: `little_endian signed long size`
|
13
14
|
little_endian signed id[32]
|
14
15
|
little_endian signed type[32]
|
15
16
|
string body[size]
|
@@ -31,3 +32,12 @@ You can also unpack strings.
|
|
31
32
|
RconPacket.structs[:packet].unpack("\v\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00hello world\x00")
|
32
33
|
# => {:size => 11, :id => 1, :type => 0, :body => "hello world"}
|
33
34
|
```
|
35
|
+
|
36
|
+
From sockets, too. Anything that responds to `#read`.
|
37
|
+
|
38
|
+
```Ruby
|
39
|
+
file = File.open("/path/to/some/file", "r")
|
40
|
+
|
41
|
+
RconPacket.structs[:packet].unpack_from_socket(file)
|
42
|
+
# => ...
|
43
|
+
```
|
data/lib/packed_struct.rb
CHANGED
@@ -1,15 +1,33 @@
|
|
1
1
|
require 'packed_struct/package'
|
2
2
|
require 'packed_struct/directive'
|
3
|
+
require 'packed_struct/modifier'
|
3
4
|
|
5
|
+
# Create structs to manage packed strings.
|
4
6
|
module PackedStruct
|
5
7
|
|
8
|
+
# The structs that were defined on the module that included this.
|
9
|
+
# If the structs were defined without a name, this will be the one
|
10
|
+
# and only struct that was defined (or the last one that was
|
11
|
+
# defined).
|
12
|
+
#
|
13
|
+
# @return [Package, Hash<Symbol, Package>]
|
6
14
|
def structs
|
7
15
|
@structs ||= {}
|
8
16
|
end
|
9
17
|
|
18
|
+
alias_method :struct, :structs
|
19
|
+
|
20
|
+
# Define a struct. The name will be used for {#structs}, and the
|
21
|
+
# block will run in the context of a {Package}.
|
22
|
+
#
|
23
|
+
# @yield []
|
24
|
+
# @param name [Symbol, nil] the name of the struct. If it's nil, it
|
25
|
+
# is set as the only struct of the included module.
|
26
|
+
# @return (see #structs)
|
10
27
|
def struct_layout(name = nil, &block)
|
11
28
|
structs[name] = Package.new
|
12
29
|
structs[name].instance_exec &block
|
30
|
+
structs[name].finalize_directives!
|
13
31
|
|
14
32
|
if name == nil
|
15
33
|
@structs = structs[name]
|
@@ -18,6 +36,11 @@ module PackedStruct
|
|
18
36
|
structs
|
19
37
|
end
|
20
38
|
|
39
|
+
# Called when this is included into another module.
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
# @param reciever [Module] the reciever that included this one.
|
43
|
+
# @return [void]
|
21
44
|
def self.included(reciever)
|
22
45
|
reciever.extend self
|
23
46
|
end
|
@@ -1,8 +1,6 @@
|
|
1
|
-
|
1
|
+
require 'set'
|
2
2
|
|
3
|
-
|
4
|
-
# of the type, the endian(ness) of the type, the type of the type
|
5
|
-
# (short, int, long, etc.), and the signed(ness) of the type.
|
3
|
+
module PackedStruct
|
6
4
|
class Directive
|
7
5
|
|
8
6
|
# The name of the directive. This is passed as the first value of
|
@@ -11,249 +9,168 @@ module PackedStruct
|
|
11
9
|
# @return [Symbol]
|
12
10
|
attr_reader :name
|
13
11
|
|
14
|
-
# The
|
15
|
-
#
|
16
|
-
# @return [Array<Object>]
|
17
|
-
attr_reader :options
|
18
|
-
|
19
|
-
# The tags the directive has, such as type, signed(ness),
|
20
|
-
# endian(ness), and size. Not filled until {#to_s} is called.
|
21
|
-
#
|
22
|
-
# @return [Hash]
|
23
|
-
attr_accessor :tags
|
24
|
-
|
25
|
-
# The children of this directive. If this directive has a parent,
|
26
|
-
# this is nil.
|
27
|
-
#
|
28
|
-
# @return [nil, Array<Directive>]
|
29
|
-
attr_reader :subs
|
30
|
-
|
31
|
-
# The parent of this directive. The relationship is such that
|
32
|
-
# +parent.subs.include?(self)+ is true. If this has a parent, it
|
33
|
-
# is nil.
|
12
|
+
# The modifiers for this directive.
|
34
13
|
#
|
35
|
-
# @return [
|
36
|
-
|
14
|
+
# @return [Set<Modifier>]
|
15
|
+
attr_reader :modifiers
|
37
16
|
|
38
|
-
#
|
17
|
+
# Metadata about the directive. Created when {#finalize!} is
|
18
|
+
# called.
|
39
19
|
#
|
40
|
-
# @return [
|
41
|
-
|
42
|
-
|
43
|
-
# @!parse
|
44
|
-
# attr_reader :value
|
45
|
-
def value
|
46
|
-
@value || (@tags[:original].value if @tags[:original])
|
47
|
-
end
|
20
|
+
# @return [Hash<Symbol, Object>]
|
21
|
+
attr_reader :tags
|
48
22
|
|
49
23
|
# Initialize the directive.
|
50
24
|
#
|
51
25
|
# @param name [Symbol] the name of the directive.
|
52
|
-
|
26
|
+
# @param package [Package] the package this directive is a part
|
27
|
+
# of.
|
28
|
+
def initialize(name)
|
53
29
|
@name = name
|
54
|
-
|
55
|
-
@
|
56
|
-
|
57
|
-
@
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
30
|
+
@modifiers = []
|
31
|
+
@finalized = true
|
32
|
+
|
33
|
+
@tags = {
|
34
|
+
:endian => :native,
|
35
|
+
:signedness => :signed,
|
36
|
+
:size => nil,
|
37
|
+
:precision => :single,
|
38
|
+
:size_mod => 0
|
39
|
+
}
|
65
40
|
end
|
66
41
|
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
42
|
+
# Determines whether or not this directive is empty. It is
|
43
|
+
# considered empty when its tags has all of the default values,
|
44
|
+
# it has no modifiers, and its name is not +:null+.
|
70
45
|
#
|
71
|
-
# @
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
else
|
77
|
-
@_str = nil
|
78
|
-
@_sub_types = nil
|
79
|
-
@subs << child
|
80
|
-
child.parent = self
|
81
|
-
child
|
82
|
-
end
|
46
|
+
# @return [Boolean]
|
47
|
+
def empty?
|
48
|
+
tags == { :endian => :native, :signedness => :signed,
|
49
|
+
:size => nil, :precision => :single, :size_mod => 0
|
50
|
+
} && modifiers.length == 0 && name != :null
|
83
51
|
end
|
84
52
|
|
85
|
-
#
|
53
|
+
# Add a modifier to this directive.
|
86
54
|
#
|
55
|
+
# @param mod [Modifier]
|
87
56
|
# @return [self]
|
88
|
-
def
|
89
|
-
@
|
57
|
+
def add_modifier(mod)
|
58
|
+
@finalized = false
|
59
|
+
modifiers << mod
|
90
60
|
self
|
91
61
|
end
|
92
62
|
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
63
|
+
# Changes the size of the directive to the given size. It is
|
64
|
+
# possible for the given value to the a directive; if it is,
|
65
|
+
# it just uses the name of the directive.
|
96
66
|
#
|
97
|
-
# @
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
# Inspects the directive.
|
108
|
-
#
|
109
|
-
# @return [String]
|
110
|
-
def inspect
|
111
|
-
"#<#{self.class.name}:#{name}>"
|
112
|
-
end
|
67
|
+
# @param new_size [Numeric, Directive]
|
68
|
+
# @return [self]
|
69
|
+
def [](new_size)
|
70
|
+
if new_size.is_a? Directive
|
71
|
+
tags.merge! new_size.tags_for_sized_directive
|
72
|
+
else
|
73
|
+
tags[:size] = new_size
|
74
|
+
end
|
113
75
|
|
114
|
-
|
115
|
-
#
|
116
|
-
# @return [Directive]
|
117
|
-
def -(other)
|
118
|
-
dir = dup
|
119
|
-
dir.tags = tags.dup
|
120
|
-
dir.tags[:original ] = self
|
121
|
-
dir.tags[:size_modify] = -other
|
122
|
-
dir
|
76
|
+
self
|
123
77
|
end
|
124
78
|
|
125
|
-
#
|
79
|
+
# Returns a hash to be merged into the tags of a directive that
|
80
|
+
# recieved this directive for a size.
|
126
81
|
#
|
127
|
-
# @return [
|
128
|
-
def
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
dir
|
82
|
+
# @return [Hash<Symbol, Object>]
|
83
|
+
def tags_for_sized_directive
|
84
|
+
{
|
85
|
+
:size => name,
|
86
|
+
:size_mod => tags[:size_mod]
|
87
|
+
}
|
134
88
|
end
|
135
89
|
|
136
|
-
#
|
90
|
+
# Modifies +self+ such that it sizes itself (on {#to_s}ing), and
|
91
|
+
# keeping this size modification in mind. In other words, this
|
92
|
+
# is meant to be used in another directive's {#[]} method for
|
93
|
+
# telling it what size it should be, and this method modifies that
|
94
|
+
# size by the given amount.
|
137
95
|
#
|
138
|
-
# @
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
4
|
147
|
-
when :long
|
148
|
-
4
|
149
|
-
when :float
|
150
|
-
if sub_names.include?(:double)
|
151
|
-
8
|
152
|
-
else
|
153
|
-
4
|
154
|
-
end
|
155
|
-
when :null
|
156
|
-
size || 1
|
157
|
-
when :string
|
158
|
-
size
|
96
|
+
# @example
|
97
|
+
# some_directive[another_directive - 5]
|
98
|
+
# @param other [Numeric, #coerce]
|
99
|
+
# @return [self, Object]
|
100
|
+
def -(other)
|
101
|
+
if other.is_a? Numeric
|
102
|
+
tags[:size_mod] = -other
|
103
|
+
self
|
159
104
|
else
|
160
|
-
|
105
|
+
self_equiv, arg_equiv = other.coerce(self)
|
106
|
+
self_equiv - arg_equiv
|
161
107
|
end
|
162
108
|
end
|
163
109
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
# @return [Array<Symbol>]
|
170
|
-
def sub_names(force = false)
|
171
|
-
if @_sub_types && !force
|
172
|
-
@_sub_types
|
110
|
+
# (see #-)
|
111
|
+
def +(other)
|
112
|
+
if other.is_a? Numeric
|
113
|
+
tags[:size_mod] = +other
|
114
|
+
self
|
173
115
|
else
|
174
|
-
|
116
|
+
self_equiv, arg_equiv = other.coerce(self)
|
117
|
+
self_equiv + arg_equiv
|
175
118
|
end
|
176
119
|
end
|
177
120
|
|
178
|
-
#
|
179
|
-
#
|
121
|
+
# Coerces +self+ into a format that can be used with +Numeric+ to
|
122
|
+
# add or subtract from this class.
|
180
123
|
#
|
181
|
-
# @
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
@tags[:size]
|
188
|
-
when nil
|
189
|
-
@subs.select { |s| s && s.tags[:size] }.map { |s| s.tags[:size] }.last
|
190
|
-
end
|
124
|
+
# @example
|
125
|
+
# some_directive[1 + another_directive]
|
126
|
+
# @param other [Object]
|
127
|
+
# @return [Array<(self, Object)>]
|
128
|
+
def coerce(other)
|
129
|
+
[self, other]
|
191
130
|
end
|
192
131
|
|
193
|
-
#
|
194
|
-
#
|
132
|
+
# Whether or not this directive has finalized. It is finalized
|
133
|
+
# until a modifier is added, and then {#finalize!} is required to
|
134
|
+
# finalize the directive.
|
195
135
|
#
|
196
|
-
# @return [
|
197
|
-
|
198
|
-
|
199
|
-
case true
|
200
|
-
when sub_names.include?(:short)
|
201
|
-
:short
|
202
|
-
when sub_names.include?(:int)
|
203
|
-
:int
|
204
|
-
when sub_names.include?(:long)
|
205
|
-
:long
|
206
|
-
when sub_names.include?(:char), sub_names.include?(:string)
|
207
|
-
:string
|
208
|
-
when sub_names.include?(:float)
|
209
|
-
:float
|
210
|
-
when sub_names.include?(:null)
|
211
|
-
:null
|
212
|
-
else
|
213
|
-
nil
|
214
|
-
end
|
136
|
+
# @return [Boolean]
|
137
|
+
def finalized?
|
138
|
+
@finalized
|
215
139
|
end
|
216
140
|
|
217
|
-
#
|
218
|
-
# to search for matching names. Defaults to +:native+.
|
141
|
+
# Finalizes the directive.
|
219
142
|
#
|
220
|
-
# @return [
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
:
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
143
|
+
# @return [void]
|
144
|
+
def finalize!
|
145
|
+
return if finalized?
|
146
|
+
|
147
|
+
modifiers.each do |modifier|
|
148
|
+
case modifier.type
|
149
|
+
when :endian, :signedness, :precision, :type, :string_type
|
150
|
+
tags[modifier.type] = modifier.value
|
151
|
+
when :size
|
152
|
+
tags[:size] = modifier.value unless tags[:size]
|
153
|
+
else
|
154
|
+
raise UnknownModifierError,
|
155
|
+
"Unknown modifier: #{modifier.type}"
|
156
|
+
end
|
233
157
|
end
|
234
|
-
end
|
235
158
|
|
236
|
-
|
237
|
-
|
238
|
-
#
|
239
|
-
# @return [Symbol] the return value can be any of +:unsigned+ or
|
240
|
-
# +:signed+.
|
241
|
-
def determine_signed
|
242
|
-
if sub_names.include?(:unsigned) && !sub_names.include?(:null)
|
243
|
-
:unsigned
|
244
|
-
else
|
245
|
-
:signed
|
246
|
-
end
|
159
|
+
@finalized = true
|
160
|
+
cache_string
|
247
161
|
end
|
248
162
|
|
249
|
-
#
|
250
|
-
# the
|
163
|
+
# Turn the directive into a string, with the given data. It
|
164
|
+
# shouldn't need the data unless +tags[:size]+ is a Symbol.
|
251
165
|
#
|
252
|
-
# @
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
166
|
+
# @param data [Hash<Symbol, Object>] the data that may be used for
|
167
|
+
# the length.
|
168
|
+
# @return [String]
|
169
|
+
def to_s(data = {})
|
170
|
+
return @_cache if !tags[:size].is_a?(Symbol) && @_cache
|
171
|
+
return "x" * (tags[:size] || 1) if name == :null
|
172
|
+
|
173
|
+
out = case tags[:type]
|
257
174
|
when :short
|
258
175
|
modify_if_needed "S"
|
259
176
|
when :int
|
@@ -264,119 +181,185 @@ module PackedStruct
|
|
264
181
|
handle_string_type
|
265
182
|
when :float
|
266
183
|
handle_float_type
|
267
|
-
when
|
268
|
-
|
184
|
+
when nil
|
185
|
+
handle_empty_type
|
269
186
|
else
|
270
187
|
nil
|
271
188
|
end
|
189
|
+
|
190
|
+
if tags[:size].is_a? Symbol
|
191
|
+
out << data.fetch(tags[:size]).to_s
|
192
|
+
elsif tags[:size] && ![:null, nil].include?(tags[:type])
|
193
|
+
out << tags[:size].to_s
|
194
|
+
end
|
195
|
+
|
196
|
+
out
|
272
197
|
end
|
273
198
|
|
274
|
-
#
|
199
|
+
# The number of bytes a type takes up in the string.
|
200
|
+
BYTES_IN_STRING = {
|
201
|
+
:char => [0].pack("c").bytesize,
|
202
|
+
:short => [0].pack("s").bytesize,
|
203
|
+
:int => [0].pack("i").bytesize,
|
204
|
+
:long => [0].pack("l").bytesize,
|
205
|
+
:float_single => [0].pack("f").bytesize,
|
206
|
+
:float_double => [0].pack("D").bytesize,
|
207
|
+
}
|
208
|
+
|
209
|
+
# The number of bytes this takes up in the resulting packed string.
|
275
210
|
#
|
276
|
-
# @
|
277
|
-
|
278
|
-
|
279
|
-
|
211
|
+
# @param (see #to_s)
|
212
|
+
# @return [Numeric]
|
213
|
+
def bytesize(data = {})
|
214
|
+
case tags[:type]
|
215
|
+
when nil
|
216
|
+
(size(data) || 8) / 8
|
217
|
+
when :short, :int, :long
|
218
|
+
BYTES_IN_STRING.fetch tags[:type]
|
219
|
+
when :float
|
220
|
+
if tags[:precision] == :double
|
221
|
+
BYTES_IN_STRING[:float_double]
|
222
|
+
else
|
223
|
+
BYTES_IN_STRING[:float_single]
|
224
|
+
end
|
225
|
+
when :null
|
226
|
+
size(data) || 1
|
227
|
+
when :string
|
228
|
+
size(data)
|
280
229
|
else
|
281
|
-
|
230
|
+
0
|
282
231
|
end
|
283
232
|
end
|
284
233
|
|
285
|
-
#
|
286
|
-
# the directive.
|
234
|
+
# The size of this directive.
|
287
235
|
#
|
236
|
+
# @param (see #to_s)
|
237
|
+
# @return [nil, Numeric]
|
238
|
+
def size(data = {})
|
239
|
+
if tags[:size].is_a? Symbol
|
240
|
+
data.fetch(tags[:size])
|
241
|
+
else
|
242
|
+
tags[:size]
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
# Tries to cache the string value of this directive. It cannot if
|
249
|
+
# +tags[:size]+ is a Symbol, since it depends on the value of the
|
250
|
+
# directive named by that symbol.
|
251
|
+
#
|
252
|
+
# @return [String]
|
253
|
+
def cache_string
|
254
|
+
return if tags[:size].is_a? Symbol
|
255
|
+
return @_cache = "x" * (tags[:size] || 1) if name == :null
|
256
|
+
|
257
|
+
@_cache = to_s
|
258
|
+
end
|
259
|
+
|
260
|
+
# Handles the type if there is no type, i.e. a type modifier was
|
261
|
+
# not specified. Can only handle directives with sizes
|
262
|
+
# 0 (default), 8, 16, 32, and 64.
|
263
|
+
#
|
264
|
+
# @see #modify_if_needed
|
288
265
|
# @return [String]
|
289
|
-
def
|
266
|
+
def handle_empty_type
|
290
267
|
maps = {
|
268
|
+
0 => "x",
|
291
269
|
8 => "C",
|
292
270
|
16 => "S",
|
293
271
|
32 => "L",
|
294
272
|
64 => "Q"
|
295
273
|
}
|
296
274
|
|
297
|
-
|
298
|
-
"Cannot make number of #{size} length" unless
|
299
|
-
maps.keys.include?(size)
|
300
|
-
|
301
|
-
modify_if_needed maps[size]
|
275
|
+
modify_if_needed maps.fetch(tags[:size] || 0), tags[:size] != 8
|
302
276
|
end
|
303
277
|
|
304
|
-
# Handles
|
305
|
-
#
|
306
|
-
#
|
307
|
-
# Otherwise, determines the type of string from the sub names;
|
308
|
-
# types of strings can include +:hex+, +:base64+, +:bit+, or
|
309
|
-
# +:binary+ (defaults to binary).
|
278
|
+
# Handles the type if it is string. Defaults to a null-padded
|
279
|
+
# string, but if a +:hex+, +:base64+, or +:bit+ modifier is
|
280
|
+
# specified, it will be used.
|
310
281
|
#
|
311
|
-
#
|
282
|
+
# If +:hex+ is specified, the endianness will be used to determine
|
283
|
+
# which nibble will go first.
|
284
|
+
#
|
285
|
+
# If +:base64+ is specified, the endianness will be used to
|
286
|
+
# determine whether +MSB+ or the +LSB+ will go first.
|
312
287
|
def handle_string_type
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
when sub_names.include?(:base64)
|
288
|
+
case tags[:string_type]
|
289
|
+
when :hex
|
290
|
+
modify_for_endianness "H", true
|
291
|
+
when :base64
|
318
292
|
"m"
|
319
|
-
when
|
320
|
-
|
293
|
+
when :bit
|
294
|
+
modify_for_endianness "B", true
|
321
295
|
else
|
322
|
-
|
296
|
+
modify_for_endianness "a", true
|
323
297
|
end
|
324
298
|
end
|
325
299
|
|
326
|
-
# Handles float
|
327
|
-
#
|
300
|
+
# Handles the float type. Handles the endianness and the
|
301
|
+
# precision, returning the correct character for the float type.
|
328
302
|
#
|
329
303
|
# @return [String]
|
330
304
|
def handle_float_type
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
when :native
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
when :
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
"e"
|
345
|
-
end
|
346
|
-
when :big
|
347
|
-
if double
|
348
|
-
"G"
|
349
|
-
else
|
350
|
-
"g"
|
351
|
-
end
|
305
|
+
case [tags[:endian], tags[:precision]]
|
306
|
+
when [:native, :double]
|
307
|
+
"D"
|
308
|
+
when [:native, :single]
|
309
|
+
"F"
|
310
|
+
when [:little, :double]
|
311
|
+
"E"
|
312
|
+
when [:little, :single]
|
313
|
+
"e"
|
314
|
+
when [:big, :double]
|
315
|
+
"G"
|
316
|
+
when [:big, :single]
|
317
|
+
"g"
|
352
318
|
end
|
353
319
|
end
|
354
320
|
|
355
|
-
# Modifies the given string
|
356
|
-
#
|
357
|
-
#
|
358
|
-
# endian, and that ">" added means big endian (and that nothing
|
359
|
-
# added stands for native).
|
321
|
+
# Modifies the given string if it's needed, according to
|
322
|
+
# signness and endianness. This assumes that a signed
|
323
|
+
# directive should be in lowercase.
|
360
324
|
#
|
361
|
-
# @param str [String]
|
325
|
+
# @param str [String] the string to modify.
|
326
|
+
# @param include_endian [Boolean] whether or not to include the
|
327
|
+
# endianness.
|
362
328
|
# @return [String]
|
363
329
|
def modify_if_needed(str, include_endian = true)
|
364
|
-
base = if @tags[:
|
365
|
-
str.
|
330
|
+
base = if @tags[:signedness] == :signed
|
331
|
+
str.swapcase
|
366
332
|
else
|
367
333
|
str
|
368
334
|
end
|
369
|
-
|
370
|
-
|
371
|
-
when :little
|
372
|
-
"<"
|
373
|
-
when :big
|
374
|
-
">"
|
335
|
+
if include_endian
|
336
|
+
modify_for_endianness(base)
|
375
337
|
else
|
376
|
-
|
377
|
-
end
|
338
|
+
base
|
339
|
+
end
|
340
|
+
end
|
378
341
|
|
379
|
-
|
342
|
+
# Modifies the given string to account for endianness. If
|
343
|
+
# +use_case+ is true, it modifies the case of the given string to
|
344
|
+
# represent endianness; otherwise, it appends data to the string
|
345
|
+
# to represent endianness.
|
346
|
+
#
|
347
|
+
# @param str [String] the string to modify.
|
348
|
+
# @param use_case [Boolean]
|
349
|
+
# @return [String]
|
350
|
+
def modify_for_endianness(str, use_case = false)
|
351
|
+
case [tags[:endian], use_case]
|
352
|
+
when [:little, true]
|
353
|
+
str.swapcase
|
354
|
+
when [:little, false]
|
355
|
+
str + "<"
|
356
|
+
when [:big, true]
|
357
|
+
str
|
358
|
+
when [:big, false]
|
359
|
+
str + ">"
|
360
|
+
else
|
361
|
+
str
|
362
|
+
end
|
380
363
|
end
|
381
364
|
|
382
365
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module PackedStruct
|
2
|
+
class Modifier
|
3
|
+
|
4
|
+
# Initializes the modifier.
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
@type = nil
|
8
|
+
@value = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
# The type of modifier it is. Has three possible values:
|
12
|
+
# +:endian+, +:size+, and +:signedness+.
|
13
|
+
#
|
14
|
+
# @return [Symbol]
|
15
|
+
# @!parse
|
16
|
+
# attr_reader :type
|
17
|
+
def type
|
18
|
+
compile! unless @compiled
|
19
|
+
@type
|
20
|
+
end
|
21
|
+
|
22
|
+
# The value of the modifier. Has multiple possible values,
|
23
|
+
# including: +:little+, +:big+, +:short+, +:int+, +:long+,
|
24
|
+
# +:float+, +:null+, +:string+, +:unsigned+, +:signed+.
|
25
|
+
#
|
26
|
+
# @return [Symbol]
|
27
|
+
# @!parse
|
28
|
+
# attr_reader :value
|
29
|
+
def value
|
30
|
+
compile! unless @compiled
|
31
|
+
@value
|
32
|
+
end
|
33
|
+
|
34
|
+
# Compiles the modifier into a usable format. Stores the values
|
35
|
+
# it determines in +@type+ and +@value+.
|
36
|
+
#
|
37
|
+
# @raises [UnknownModifierError] if it can't detect the type of
|
38
|
+
# modifier.
|
39
|
+
# @return [void]
|
40
|
+
def compile!
|
41
|
+
@compiled ||= begin
|
42
|
+
case @name
|
43
|
+
when :little_endian, :little, :lsb, :low
|
44
|
+
@type = :endian
|
45
|
+
@value = :little
|
46
|
+
when :big_endian, :big, :msb, :high, :network
|
47
|
+
@type = :endian
|
48
|
+
@value = :big
|
49
|
+
when :short, :int, :long, :float, :string
|
50
|
+
@type = :type
|
51
|
+
@value = @name
|
52
|
+
when :unsigned, :signed
|
53
|
+
@type = :signedness
|
54
|
+
@value = @name
|
55
|
+
when :null
|
56
|
+
@type = :signedness
|
57
|
+
@value = :signed
|
58
|
+
when :spaced
|
59
|
+
@type = :signedness
|
60
|
+
@value = :unsigned
|
61
|
+
when :double
|
62
|
+
@type = :length
|
63
|
+
@value = :double
|
64
|
+
when :hex, :base64, :bit
|
65
|
+
@type = :string_type
|
66
|
+
@value = @name
|
67
|
+
else
|
68
|
+
raise UnknownModifierError, "Unkown modifier: #{@name}"
|
69
|
+
end
|
70
|
+
true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class UnknownModifierError < StandardError; end
|
76
|
+
end
|
@@ -9,7 +9,6 @@ module PackedStruct
|
|
9
9
|
#
|
10
10
|
# @return [Array<Directive>]
|
11
11
|
def directives
|
12
|
-
@directives = @directives.select { |x| x.parent.nil? }
|
13
12
|
@directives
|
14
13
|
end
|
15
14
|
|
@@ -21,9 +20,11 @@ module PackedStruct
|
|
21
20
|
# Turn the package into a string. Uses the directives (calls
|
22
21
|
# {Directive#to_s} on them), and joins the result.
|
23
22
|
#
|
23
|
+
# @param data [Hash<Symbol, Object>] the data to pass to
|
24
|
+
# {Directive#to_s}.
|
24
25
|
# @return [String] the string ready for #pack.
|
25
|
-
def to_s
|
26
|
-
directives.map(
|
26
|
+
def to_s(data = {})
|
27
|
+
directives.map { |x| x.to_s(data) }.join(' ')
|
27
28
|
end
|
28
29
|
|
29
30
|
alias_method :to_str, :to_s
|
@@ -31,7 +32,7 @@ module PackedStruct
|
|
31
32
|
# Packs the given data into a string. The keys of the data
|
32
33
|
# correspond to the names of the directives.
|
33
34
|
#
|
34
|
-
# @param [Hash<Symbol, Object>] the data.
|
35
|
+
# @param data [Hash<Symbol, Object>] the data.
|
35
36
|
# @return [String] the packed data.
|
36
37
|
def pack(data)
|
37
38
|
values = []
|
@@ -44,23 +45,11 @@ module PackedStruct
|
|
44
45
|
values = values.select { |x| mapped_directives.include?(x[0]) }
|
45
46
|
|
46
47
|
values.sort! do |a, b|
|
47
|
-
|
48
|
+
mapped_directives.index(a[0]) <=> mapped_directives.index(b[0])
|
48
49
|
end
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
# Packs the directives into a string. Uses an array.
|
54
|
-
# The parameters can either be an array, or a set of values.
|
55
|
-
#
|
56
|
-
# @return [String]
|
57
|
-
def pack_with_array(*array)
|
58
|
-
array.flatten!
|
59
|
-
|
60
|
-
directives.each_with_index { |e, i| e.value = array[i] }
|
61
|
-
out = array.pack(self.to_s)
|
62
|
-
directives.each { |x| x.value = nil }
|
63
|
-
out
|
51
|
+
ary = values.map(&:last)
|
52
|
+
ary.pack to_s(data)
|
64
53
|
end
|
65
54
|
|
66
55
|
# Unpacks the given string with the directives. Returns a hash
|
@@ -73,18 +62,14 @@ module PackedStruct
|
|
73
62
|
total = ""
|
74
63
|
parts = {}
|
75
64
|
directives.each_with_index do |directive, i|
|
76
|
-
total << directive.to_s
|
77
|
-
|
78
|
-
directive.value = value
|
79
|
-
parts[directive.name] = value
|
65
|
+
total << directive.to_s(parts)
|
66
|
+
parts[directive.name] = string.unpack(total)[i]
|
80
67
|
end
|
81
68
|
|
82
|
-
|
83
|
-
directives.each { |x| x.value = nil }
|
84
|
-
|
85
69
|
parts.delete(:null) {}
|
86
70
|
parts
|
87
71
|
end
|
72
|
+
|
88
73
|
# Unpacks from a socket.
|
89
74
|
#
|
90
75
|
# @param sock [#read] the socket to unpack from.
|
@@ -95,15 +80,11 @@ module PackedStruct
|
|
95
80
|
parts = {}
|
96
81
|
|
97
82
|
directives.each_with_index do |directive, i|
|
98
|
-
total << directive.to_s
|
99
|
-
read << sock.read(directive.bytesize)
|
100
|
-
|
101
|
-
directive.value = value
|
102
|
-
parts[directive.name] = value
|
83
|
+
total << directive.to_s(parts)
|
84
|
+
read << sock.read(directive.bytesize parts)
|
85
|
+
parts[directive.name] = read.unpack(total)[i]
|
103
86
|
end
|
104
87
|
|
105
|
-
directives.each { |x| x.value = nil }
|
106
|
-
|
107
88
|
parts.delete(:null) {}
|
108
89
|
parts
|
109
90
|
end
|
@@ -126,6 +107,14 @@ module PackedStruct
|
|
126
107
|
parts
|
127
108
|
end
|
128
109
|
|
110
|
+
# Finalizes all of the directives.
|
111
|
+
#
|
112
|
+
# @return [void]
|
113
|
+
def finalize_directives!
|
114
|
+
@finalized = true
|
115
|
+
directives.reject!(&:empty?)
|
116
|
+
directives.map(&:finalize!)
|
117
|
+
end
|
129
118
|
|
130
119
|
# Inspects the package.
|
131
120
|
#
|
@@ -138,12 +127,11 @@ module PackedStruct
|
|
138
127
|
#
|
139
128
|
# @return [Directive] the new directive.
|
140
129
|
def method_missing(method, *arguments, &block)
|
141
|
-
if @
|
142
|
-
|
130
|
+
super if @finalized
|
131
|
+
if arguments.length == 1 && arguments.first.is_a?(Directive)
|
132
|
+
arguments.first.add_modifier Modifier.new(method)
|
143
133
|
else
|
144
|
-
|
145
|
-
@directives << directive
|
146
|
-
directive
|
134
|
+
(directives.push Directive.new(method)).last
|
147
135
|
end
|
148
136
|
end
|
149
137
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: packed_struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-06-
|
12
|
+
date: 2013-06-27 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: ! ' Cleans up the string mess when packing items (in Array#pack) and
|
15
15
|
unpacking items (in String#unpack).
|
@@ -23,6 +23,7 @@ files:
|
|
23
23
|
- README.md
|
24
24
|
- LICENSE
|
25
25
|
- lib/packed_struct/version.rb
|
26
|
+
- lib/packed_struct/modifier.rb
|
26
27
|
- lib/packed_struct/package.rb
|
27
28
|
- lib/packed_struct/directive.rb
|
28
29
|
- lib/packed_struct.rb
|