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 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
- module PackedStruct
1
+ require 'set'
2
2
 
3
- # Contains information about a directive. A directive can be a name
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 arguments passed to the directive.
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 [nil, Directive]
36
- attr_accessor :parent
14
+ # @return [Set<Modifier>]
15
+ attr_reader :modifiers
37
16
 
38
- # The value this directive holds. Only for use when packing.
17
+ # Metadata about the directive. Created when {#finalize!} is
18
+ # called.
39
19
  #
40
- # @return [nil, Object]
41
- attr_writer :value
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
- def initialize(name, *arguments)
26
+ # @param package [Package] the package this directive is a part
27
+ # of.
28
+ def initialize(name)
53
29
  @name = name
54
-
55
- @options = arguments
56
- @tags = {}
57
- @subs = []
58
- @parent = nil
59
- @value = nil
60
-
61
- if arguments.first.is_a? Directive
62
- arguments.first.add_child(self)
63
- @subs = nil
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
- # Add a child to this (or its parent's) directive. If this is a
68
- # child itself, it adds it to the parent of this. Invalidates the
69
- # caches for {#sub_names} and {#to_s}
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
- # @param child [Directive] the child to add.
72
- # @return [Directive] the child.
73
- def add_child(child)
74
- if @parent
75
- @parent.add_child(child)
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
- # Set the size of this directive.
53
+ # Add a modifier to this directive.
86
54
  #
55
+ # @param mod [Modifier]
87
56
  # @return [self]
88
- def [](size)
89
- @tags[:size] = size
57
+ def add_modifier(mod)
58
+ @finalized = false
59
+ modifiers << mod
90
60
  self
91
61
  end
92
62
 
93
- # Turn the directive into a string. Analyzes the subs before
94
- # determining information, then outputs that. Caches the value
95
- # until {#add_child} is next called.
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
- # @return [String]
98
- def to_s
99
- return "" unless @subs
100
- @subs.compact!
101
- @tags[:signed] = determine_signed
102
- @tags[:type] = determine_type
103
- @tags[:endian] = determine_endian
104
- "#{make_directive}#{make_length}"
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
- # To show the size of something else, relative to this directive.
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
- # To show the size of something else, relative to this directive.
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 [Directive]
128
- def +(other)
129
- dir = dup
130
- dir.tags = tags.dup
131
- dir.tags[:original ] = self
132
- dir.tags[:size_modify] = +other
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
- # The number of bytes this takes up in the resulting packed string.
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
- # @return [Numeric]
139
- def bytesize
140
- case @tags[:type]
141
- when nil
142
- size / 8
143
- when :short
144
- 2
145
- when :int
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
- 0
105
+ self_equiv, arg_equiv = other.coerce(self)
106
+ self_equiv - arg_equiv
161
107
  end
162
108
  end
163
109
 
164
- private
165
-
166
- # Returns all of the names of the subs, and caches it.
167
- #
168
- # @param force [Boolean] force reloading the names of the subs.
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
- @subs.map(&:name) + [@name]
116
+ self_equiv, arg_equiv = other.coerce(self)
117
+ self_equiv + arg_equiv
175
118
  end
176
119
  end
177
120
 
178
- # Determines the size of the directive by checking if it's in the
179
- # tags, or by searching the subs.
121
+ # Coerces +self+ into a format that can be used with +Numeric+ to
122
+ # add or subtract from this class.
180
123
  #
181
- # @return [Numeric]
182
- def size
183
- case @tags[:size]
184
- when Directive
185
- (@tags[:size].value || 0).to_i + (@tags[:size].tags[:size_modify] || 0).to_i
186
- when Numeric
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
- # Determine the type of this directive. Uses {#sub_names} to
194
- # search for matching types. Defaults to +nil+.
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 [nil, Symbol] the return value can be any of +:short+,
197
- # +:int+, +:long+, +:string+, +:float+, or +nil+.
198
- def determine_type
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
- # Determines the endianness of this directive. Uses {#sub_names}
218
- # to search for matching names. Defaults to +:native+.
141
+ # Finalizes the directive.
219
142
  #
220
- # @return [Symbol] the return value can be any of +:little+,
221
- # +:big+, or +:native+.
222
- def determine_endian
223
- case true
224
- when sub_names.include?(:little), sub_names.include?(:little_endian),
225
- sub_names.include?(:lsb), sub_names.include?(:low)
226
- :little
227
- when sub_names.include?(:big), sub_names.include?(:big_endian),
228
- sub_names.include?(:msb), sub_names.include?(:high),
229
- sub_names.include?(:network)
230
- :big
231
- else
232
- :native
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
- # Determines the signedness of this directive. Uses {#sub_names}
237
- # to search for matching names. Defaults to +:signed+.
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
- # Determines the directive to be used in the pack string, using
250
- # the type from {#determine_type} to manage it.
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
- # @return [String] the directive for the pack string.
253
- def make_directive
254
- case @tags[:type]
255
- when nil
256
- handle_nil_type
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 :null
268
- "x" * (size || 1)
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
- # Determines the length to be added to the pack string.
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
- # @return [String]
277
- def make_length
278
- if size && ![:null, nil].include?(@tags[:type])
279
- size.to_s
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
- # Handles the nil type. Uses the size to match the type with
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 handle_nil_type
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
- raise StandardError,
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 a string type. If the name of the directive is
305
- # +:null+, returns a string containing a number of +x+s (nulls)
306
- # exactly equal to the size (or 1, if it doesn't exist).
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
- # @return [String]
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
- case true
315
- when sub_names.include?(:hex)
316
- modify_if_needed "H"
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 sub_names.include?(:bit)
320
- modify_if_needed "B", false
293
+ when :bit
294
+ modify_for_endianness "B", true
321
295
  else
322
- modify_if_needed "A", false
296
+ modify_for_endianness "a", true
323
297
  end
324
298
  end
325
299
 
326
- # Handles float types. Can handle double- or single- precision
327
- # floats, and manage their byte order.
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
- double = sub_names.include?(:double)
332
-
333
- case @tags[:endian]
334
- when :native
335
- if double
336
- "D"
337
- else
338
- "F"
339
- end
340
- when :little
341
- if double
342
- "E"
343
- else
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 as needed; it assumes that a lowercase
356
- # letter stands for a signed type and the given string stands for
357
- # an unsigned type. It also assumes that "<" added means little
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[:signed] == :signed
365
- str.downcase
330
+ base = if @tags[:signedness] == :signed
331
+ str.swapcase
366
332
  else
367
333
  str
368
334
  end
369
-
370
- base += case @tags[:endian]
371
- when :little
372
- "<"
373
- when :big
374
- ">"
335
+ if include_endian
336
+ modify_for_endianness(base)
375
337
  else
376
- ""
377
- end if include_endian
338
+ base
339
+ end
340
+ end
378
341
 
379
- base
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(&:to_s).join(' ')
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
- o = mapped_directives.index(a[0]) <=> mapped_directives.index(b[0])
48
+ mapped_directives.index(a[0]) <=> mapped_directives.index(b[0])
48
49
  end
49
50
 
50
- pack_with_array(values.map(&:last))
51
- end
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
- value = string.unpack(total)[i]
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
- value = read.unpack(total)[i]
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 @directives.map(&:name).include?(method) && arguments.length == 0
142
- @directives.select { |x| x.name == method }.first
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
- directive = Directive.new(method, *arguments)
145
- @directives << directive
146
- directive
134
+ (directives.push Directive.new(method)).last
147
135
  end
148
136
  end
149
137
 
@@ -1,5 +1,5 @@
1
1
  module PackedStruct
2
2
 
3
3
  # The current version of PackedStruct.
4
- VERSION = "0.2.1".freeze
4
+ VERSION = "0.3.0".freeze
5
5
  end
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.2.1
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-26 00:00:00.000000000 Z
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