packed_struct 0.1.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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013 Jeremy Rodi
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # PackedStruct
2
+
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
+ It was created after @charliesome suggested [a format](https://gist.github.com/redjazz96/6dda0554f62e4f77253a) for defining these strings, but never finished it.
5
+
6
+ The basic way of defining a packed struct is such:
7
+
8
+ ```Ruby
9
+ class RconPacket
10
+ include PackedStruct
11
+ struct_layout :packet do
12
+ little_endian signed size[32] # defaults to a number of size 32.
13
+ little_endian signed id[32]
14
+ little_endian signed type[32]
15
+ string body[size]
16
+ null
17
+ end
18
+ end
19
+ ```
20
+
21
+ This can be accessed as:
22
+
23
+ ```Ruby
24
+ RconPacket.structs[:packet].pack(size: 11, id: 1, type: 0, body: "hello world")
25
+ # => "\v\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00hello world\x00"
26
+ ```
27
+
28
+ You can also unpack strings.
29
+
30
+ ```Ruby
31
+ RconPacket.structs[:packet].unpack("\v\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00hello world\x00")
32
+ # => {:size => 11, :id => 1, :type => 0, :body => "hello world"}
33
+ ```
@@ -0,0 +1,25 @@
1
+ require 'packed_struct/package'
2
+ require 'packed_struct/directive'
3
+
4
+ module PackedStruct
5
+
6
+ def structs
7
+ @structs ||= {}
8
+ end
9
+
10
+ def struct_layout(name = nil, &block)
11
+ structs[name] = Package.new
12
+ structs[name].instance_exec &block
13
+
14
+ if name == nil
15
+ @structs = structs[name]
16
+ end
17
+
18
+ structs
19
+ end
20
+
21
+ def self.included(reciever)
22
+ reciever.extend self
23
+ end
24
+
25
+ end
@@ -0,0 +1,355 @@
1
+ module PackedStruct
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.
6
+ class Directive
7
+
8
+ # The name of the directive. This is passed as the first value of
9
+ # the directive; from {Package}, it is the name of the method call.
10
+ #
11
+ # @return [Symbol]
12
+ attr_reader :name
13
+
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.
34
+ #
35
+ # @return [nil, Directive]
36
+ attr_accessor :parent
37
+
38
+ # The value this directive holds. Only for use when packing.
39
+ #
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
48
+
49
+ # Initialize the directive.
50
+ #
51
+ # @param name [Symbol] the name of the directive.
52
+ def initialize(name, *arguments)
53
+ @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
65
+ end
66
+
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}
70
+ #
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
83
+ end
84
+
85
+ # Set the size of this directive.
86
+ #
87
+ # @return [self]
88
+ def [](size)
89
+ @tags[:size] = size
90
+ self
91
+ end
92
+
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.
96
+ #
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
113
+
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
123
+ end
124
+
125
+ # To show the size of something else, relative to this directive.
126
+ #
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
134
+ end
135
+
136
+ private
137
+
138
+ # Returns all of the names of the subs, and caches it.
139
+ #
140
+ # @param force [Boolean] force reloading the names of the subs.
141
+ # @return [Array<Symbol>]
142
+ def sub_names(force = false)
143
+ if @_sub_types && !force
144
+ @_sub_types
145
+ else
146
+ @subs.map(&:name) + [@name]
147
+ end
148
+ end
149
+
150
+ # Determines the size of the directive by checking if it's in the
151
+ # tags, or by searching the subs.
152
+ #
153
+ # @return [Numeric]
154
+ def size
155
+ case @tags[:size]
156
+ when Directive
157
+ (@tags[:size].value || 0) + (@tags[:size].tags[:size_modify] || 0)
158
+ when Numeric
159
+ @tags[:size]
160
+ when nil
161
+ @subs.select { |s| s && s.tags[:size] }.map { |s| s.tags[:size] }.last
162
+ end
163
+ end
164
+
165
+ # Determine the type of this directive. Uses {#sub_names} to
166
+ # search for matching types. Defaults to +nil+.
167
+ #
168
+ # @return [nil, Symbol] the return value can be any of +:short+,
169
+ # +:int+, +:long+, +:string+, +:float+, or +nil+.
170
+ def determine_type
171
+ case true
172
+ when sub_names.include?(:short)
173
+ :short
174
+ when sub_names.include?(:int)
175
+ :int
176
+ when sub_names.include?(:long)
177
+ :long
178
+ when sub_names.include?(:char), sub_names.include?(:string)
179
+ :string
180
+ when sub_names.include?(:float)
181
+ :float
182
+ when sub_names.include?(:null)
183
+ :null
184
+ else
185
+ nil
186
+ end
187
+ end
188
+
189
+ # Determines the endianness of this directive. Uses {#sub_names}
190
+ # to search for matching names. Defaults to +:native+.
191
+ #
192
+ # @return [Symbol] the return value can be any of +:little+,
193
+ # +:big+, or +:native+.
194
+ def determine_endian
195
+ case true
196
+ when sub_names.include?(:little), sub_names.include?(:little_endian),
197
+ sub_names.include?(:lsb), sub_names.include?(:low)
198
+ :little
199
+ when sub_names.include?(:big), sub_names.include?(:big_endian),
200
+ sub_names.include?(:msb), sub_names.include?(:high),
201
+ sub_names.include?(:network)
202
+ :big
203
+ else
204
+ :native
205
+ end
206
+ end
207
+
208
+ # Determines the signedness of this directive. Uses {#sub_names}
209
+ # to search for matching names. Defaults to +:signed+.
210
+ #
211
+ # @return [Symbol] the return value can be any of +:unsigned+ or
212
+ # +:signed+.
213
+ def determine_signed
214
+ if sub_names.include?(:unsigned) && !sub_names.include?(:null)
215
+ :unsigned
216
+ else
217
+ :signed
218
+ end
219
+ end
220
+
221
+ # Determines the directive to be used in the pack string, using
222
+ # the type from {#determine_type} to manage it.
223
+ #
224
+ # @return [String] the directive for the pack string.
225
+ def make_directive
226
+ case @tags[:type]
227
+ when nil
228
+ handle_nil_type
229
+ when :short
230
+ modify_if_needed "S"
231
+ when :int
232
+ modify_if_needed "I"
233
+ when :long
234
+ modify_if_needed "L"
235
+ when :string
236
+ handle_string_type
237
+ when :float
238
+ handle_float_type
239
+ when :null
240
+ "x" * (size || 1)
241
+ else
242
+ nil
243
+ end
244
+ end
245
+
246
+ # Determines the length to be added to the pack string.
247
+ #
248
+ # @return [String]
249
+ def make_length
250
+ if size && ![:null, nil].include?(@tags[:type])
251
+ size.to_s
252
+ else
253
+ ""
254
+ end
255
+ end
256
+
257
+ # Handles the nil type. Uses the size to match the type with
258
+ # the directive.
259
+ #
260
+ # @return [String]
261
+ def handle_nil_type
262
+ maps = {
263
+ 8 => "C",
264
+ 16 => "S",
265
+ 32 => "L",
266
+ 64 => "Q"
267
+ }
268
+
269
+ raise StandardError,
270
+ "Cannot make number of #{size} length" unless
271
+ maps.keys.include?(size)
272
+
273
+ modify_if_needed maps[size]
274
+ end
275
+
276
+ # Handles a string type. If the name of the directive is
277
+ # +:null+, returns a string containing a number of +x+s (nulls)
278
+ # exactly equal to the size (or 1, if it doesn't exist).
279
+ # Otherwise, determines the type of string from the sub names;
280
+ # types of strings can include +:hex+, +:base64+, +:bit+, or
281
+ # +:binary+ (defaults to binary).
282
+ #
283
+ # @return [String]
284
+ def handle_string_type
285
+
286
+ case true
287
+ when sub_names.include?(:hex)
288
+ modify_if_needed "H"
289
+ when sub_names.include?(:base64)
290
+ "m"
291
+ when sub_names.include?(:bit)
292
+ modify_if_needed "B", false
293
+ else
294
+ modify_if_needed "A", false
295
+ end
296
+ end
297
+
298
+ # Handles float types. Can handle double- or single- precision
299
+ # floats, and manage their byte order.
300
+ #
301
+ # @return [String]
302
+ def handle_float_type
303
+ double = sub_names.include?(:double)
304
+
305
+ case @tags[:endian]
306
+ when :native
307
+ if double
308
+ "D"
309
+ else
310
+ "F"
311
+ end
312
+ when :little
313
+ if double
314
+ "E"
315
+ else
316
+ "e"
317
+ end
318
+ when :big
319
+ if double
320
+ "G"
321
+ else
322
+ "g"
323
+ end
324
+ end
325
+ end
326
+
327
+ # Modifies the given string as needed; it assumes that a lowercase
328
+ # letter stands for a signed type and the given string stands for
329
+ # an unsigned type. It also assumes that "<" added means little
330
+ # endian, and that ">" added means big endian (and that nothing
331
+ # added stands for native).
332
+ #
333
+ # @param str [String]
334
+ # @return [String]
335
+ def modify_if_needed(str, include_endian = true)
336
+ base = if @tags[:signed] == :signed
337
+ str.downcase
338
+ else
339
+ str
340
+ end
341
+
342
+ base += case @tags[:endian]
343
+ when :little
344
+ "<"
345
+ when :big
346
+ ">"
347
+ else
348
+ ""
349
+ end if include_endian
350
+
351
+ base
352
+ end
353
+
354
+ end
355
+ end
@@ -0,0 +1,127 @@
1
+ module PackedStruct
2
+
3
+ # Manages the struct overall, and keeps track of the directives.
4
+ # Directives are packed in the order that they are joined, such
5
+ # that the first one defined is the first one on the string.
6
+ class Package
7
+
8
+ # The list of directives that the package has.
9
+ #
10
+ # @return [Array<Directive>]
11
+ def directives
12
+ @directives.select! { |x| x.parent.nil? }
13
+ @directives
14
+ end
15
+
16
+ # Initialize the package.
17
+ def initialize
18
+ @directives = []
19
+ end
20
+
21
+ # Turn the package into a string. Uses the directives (calls
22
+ # {Directive#to_s} on them), and joins the result.
23
+ #
24
+ # @return [String] the string ready for #pack.
25
+ def to_s
26
+ @_str ||= directives.map(&:to_s).join(' ')
27
+ end
28
+
29
+ alias_method :to_str, :to_s
30
+
31
+ # Packs the given data into a string. The keys of the data
32
+ # correspond to the names of the directives.
33
+ #
34
+ # @param [Hash<Symbol, Object>] the data.
35
+ # @return [String] the packed data.
36
+ def pack(data)
37
+ values = []
38
+ data.each do |k, v|
39
+ values.push([k, v])
40
+ end
41
+
42
+ mapped_directives = @directives.map(&:name)
43
+
44
+ values.sort do |a, b|
45
+ directives.index(a) <=> directives.index(b)
46
+ end
47
+
48
+ pack_with_array(values.map(&:last))
49
+ end
50
+
51
+ # Packs the directives into a string. Uses an array.
52
+ # The parameters can either be an array, or a set of values.
53
+ #
54
+ # @return [String]
55
+ def pack_with_array(*array)
56
+ array.flatten!
57
+
58
+ directives.each_with_index { |e, i| e.value = array[i] }
59
+ out = array.pack(self.to_s)
60
+ directives.each { |x| x.value = nil }
61
+ out
62
+ end
63
+
64
+ # Unpacks the given string with the directives. Returns a hash
65
+ # containing the values, with the keys being the names of the
66
+ # directives.
67
+ #
68
+ # @param string [String] the packed string.
69
+ # @return [Hash<Symbol, Object>] the unpacked data.
70
+ def unpack(string)
71
+ total = ""
72
+ parts = {}
73
+ directives.each_with_index do |directive, i|
74
+ total << directive.to_s
75
+ value = string.unpack(total)[i]
76
+ directive.value = value
77
+ parts[directive.name] = value
78
+ end
79
+
80
+ directives.each { |x| x.value = nil }
81
+
82
+ parts.delete(:null) {}
83
+ parts
84
+ end
85
+
86
+ # This unpacks the entire string at once. It assumes that none of
87
+ # the directives will need the values of other directives. If
88
+ # you're not sure what this means, don't use it.
89
+ #
90
+ # @param string [String] the packed string.
91
+ # @return [Hash<Symbol, Object>] the unpacked data.
92
+ def fast_unpack(string)
93
+ out = string.unpack(to_s)
94
+ parts = {}
95
+
96
+ directives.each_with_index do |directive, i|
97
+ parts[directive.name] = out[i]
98
+ end
99
+
100
+ parts.delete(:null) {}
101
+ parts
102
+ end
103
+
104
+
105
+ # Inspects the package.
106
+ #
107
+ # @return [String]
108
+ def inspect
109
+ "#<#{self.class.name}:#{"0x%014x" % directives.map(&:object_id).inject(&:+)}>"
110
+ end
111
+
112
+ # Creates a new directive with the given method and arguments.
113
+ #
114
+ # @return [Directive] the new directive.
115
+ def method_missing(method, *arguments, &block)
116
+ if @directives.map(&:name).include?(method) && arguments.length == 0
117
+ @directives.select { |x| x.name == method }.first
118
+ else
119
+ @_str = nil
120
+ directive = Directive.new(method, *arguments)
121
+ @directives << directive
122
+ directive
123
+ end
124
+ end
125
+
126
+ end
127
+ end
@@ -0,0 +1,5 @@
1
+ module PackedStruct
2
+
3
+ # The current version of PackedStruct.
4
+ VERSION = "0.1.0".freeze
5
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: packed_struct
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jeremy Rodi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-26 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! ' Cleans up the string mess when packing items (in Array#pack) and
15
+ unpacking items (in String#unpack).
16
+
17
+ '
18
+ email: redjazz96@gmail.com
19
+ executables: []
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - README.md
24
+ - LICENSE
25
+ - lib/packed_struct/version.rb
26
+ - lib/packed_struct/package.rb
27
+ - lib/packed_struct/directive.rb
28
+ - lib/packed_struct.rb
29
+ homepage: http://github.com/redjazz96/packed_struct
30
+ licenses: []
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 1.8.25
50
+ signing_key:
51
+ specification_version: 3
52
+ summary: Cleans up the string mess when packing items.
53
+ test_files: []
54
+ has_rdoc: false