packed_struct 0.2.1 → 0.3.0

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/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