packed_struct 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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