packable 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +13 -0
- data/LICENSE +26 -0
- data/README.rdoc +246 -0
- data/VERSION.yml +4 -0
- data/lib/packable.rb +9 -0
- data/lib/packable/extensions/array.rb +47 -0
- data/lib/packable/extensions/float.rb +37 -0
- data/lib/packable/extensions/integer.rb +44 -0
- data/lib/packable/extensions/io.rb +96 -0
- data/lib/packable/extensions/object.rb +14 -0
- data/lib/packable/extensions/proc.rb +16 -0
- data/lib/packable/extensions/string.rb +34 -0
- data/lib/packable/mixin.rb +57 -0
- data/lib/packable/packers.rb +107 -0
- data/test/packing_doc_test.rb +106 -0
- data/test/packing_test.rb +93 -0
- data/test/test_helper.rb +8 -0
- metadata +88 -0
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
= Packable --- History
|
2
|
+
|
3
|
+
== Version 1.2 - April 2nd, 2009
|
4
|
+
Compatible with ruby 1.9.1.
|
5
|
+
The 'jungle_survival_kit' is now in its own 'backports' gem.
|
6
|
+
|
7
|
+
== Version 1.1 - December 17, 2008
|
8
|
+
Fixed bug when packing objects implementing to_ary
|
9
|
+
Added inheritance of shortcuts & filters to documentation
|
10
|
+
|
11
|
+
== Version 1.0 - December 17, 2008
|
12
|
+
|
13
|
+
=== Initial release.
|
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# packable library
|
2
|
+
# Copyright (c) 2008, Marc-André Lafortune.
|
3
|
+
# All rights reserved.
|
4
|
+
# Licensed under the terms of the (modified) BSD License below:
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
8
|
+
# * Redistributions of source code must retain the above copyright
|
9
|
+
# notice, this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright
|
11
|
+
# notice, this list of conditions and the following disclaimer in the
|
12
|
+
# documentation and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of the author nor the
|
14
|
+
# names of its contributors may be used to endorse or promote products
|
15
|
+
# derived from this software without specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY
|
18
|
+
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
19
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
21
|
+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
22
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
23
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
24
|
+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
25
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
26
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
= Packable Library - Intro
|
2
|
+
|
3
|
+
If you need to do read and write binary data, there is of course <tt>Array::pack</tt> and <tt>String::unpack</tt>.
|
4
|
+
The packable library makes (un)packing nicer, smarter and more powerful.
|
5
|
+
In case you are wondering why on earth someone would want to do serious (un)packing when YAML & XML are built-in:
|
6
|
+
I wrote this library to read and write FLV files...
|
7
|
+
|
8
|
+
== Feature summary:
|
9
|
+
|
10
|
+
=== Explicit forms
|
11
|
+
Strings, integers & floats have long forms instead of the cryptic letter notation. For example:
|
12
|
+
["answer", 42].pack("C3n")
|
13
|
+
can be written as:
|
14
|
+
["answer", 42].pack({:bytes => 3}, {:bytes => 2, :endian => :big})
|
15
|
+
This can look a bit too verbose, so let's introduce shortcuts right away:
|
16
|
+
=== Shortcuts
|
17
|
+
Most commonly used options have shortcuts and you can define your own. For example:
|
18
|
+
:unsigned_long <===> {:bytes => 4, :signed => false, :endian => :big}
|
19
|
+
=== IO
|
20
|
+
IO classes (File & StringIO) can use (un)packing routines.
|
21
|
+
For example:
|
22
|
+
signature, block_len, temperature = my_file >> [String, :bytes=>3] >> Integer >> :float
|
23
|
+
The method +each+ also accepts packing options:
|
24
|
+
StringIO.new("\000\001\000\002\000\003").each(:short).to_a ===> [1,2,3]
|
25
|
+
=== Custom classes
|
26
|
+
It's easy to make you own classes (un)packable. All the previous goodies are thus available:
|
27
|
+
File.open("great_flick.flv") do |f|
|
28
|
+
head = f.read(FLV::Header)
|
29
|
+
f.each(FLV::Tag) do |tag|
|
30
|
+
# do something meaningful with each tag...
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
=== Filters
|
35
|
+
It's also easy to define special shortcuts that will call blocks to (un)pack any classe.
|
36
|
+
As an example, this could be useful to add special packing features to String (without monkey patching String::pack).
|
37
|
+
|
38
|
+
== Installation
|
39
|
+
|
40
|
+
First, ensure that you're running at least RubyGems 1.2 (check <tt>gem --version</tt> if you're not sure -- to update: <tt>sudo gem update --system</tt>).
|
41
|
+
|
42
|
+
Add GitHub to your gem sources (if you haven't already):
|
43
|
+
|
44
|
+
sudo gem sources -a http://gems.github.com
|
45
|
+
|
46
|
+
Get the gem:
|
47
|
+
|
48
|
+
sudo gem install marcandre-packable
|
49
|
+
|
50
|
+
That's it! Simply <tt>require 'packable'</tt> in your code to use it.
|
51
|
+
|
52
|
+
== Compatibility
|
53
|
+
|
54
|
+
Designed to work with ruby 1.8 & 1.9.
|
55
|
+
|
56
|
+
= Documentation
|
57
|
+
|
58
|
+
== Packing and unpacking
|
59
|
+
|
60
|
+
The library was designed to be backward compatible, so the usual packing and unpacking methods still work as before.
|
61
|
+
All packable objects can also be packed directly (no need to use an array). For example:
|
62
|
+
|
63
|
+
42.pack("n") ===> "\000*"
|
64
|
+
|
65
|
+
In a similar fashion, unpacking can done using class methods:
|
66
|
+
|
67
|
+
Integer.unpack("\000*", "n") ===> 42
|
68
|
+
|
69
|
+
== Formats
|
70
|
+
|
71
|
+
Although the standard string formats can still be used, it is possible to pass a list of options (see example in feature summary).
|
72
|
+
These are the options for core types:
|
73
|
+
|
74
|
+
=== Integer
|
75
|
+
[+bytes+] Number of bytes (default is 4) to use.
|
76
|
+
[+endian+] Either <tt>:big</tt> (or :network, default) or <tt>:little</tt>.
|
77
|
+
[+signed+] Either +true+ (default) or +false+. This will make a difference only when unpacking.
|
78
|
+
|
79
|
+
=== Float
|
80
|
+
[+precision+] Either <tt>:single</tt> (default) or <tt>:double</tt>.
|
81
|
+
[+endian+] Either <tt>:big</tt> (or :network, default) or <tt>:little</tt>.
|
82
|
+
|
83
|
+
=== String
|
84
|
+
[+bytes+] Total length (default is the full length)
|
85
|
+
[+fill+] The string to use for filling when packing a string shorter than the specified bytes option. Default is a space.
|
86
|
+
|
87
|
+
=== Array
|
88
|
+
[+repeat+] This option can be used (when packing only) to repeat the current option. A value of <tt>:all</tt> will mean for all remaining elements of the array.
|
89
|
+
|
90
|
+
When unpacking, it is necessary to specify the class in addition to any option, like so:
|
91
|
+
|
92
|
+
"AB".unpack(Integer, :bytes => 2, :endian => :big, :signed => false) ===> 0x3132
|
93
|
+
|
94
|
+
== Shortcuts and default values
|
95
|
+
|
96
|
+
It's easy to add shortcuts for easier (un)packing:
|
97
|
+
|
98
|
+
String.packers.set :flv_signature, :bytes => 3, :fill => "FLV"
|
99
|
+
|
100
|
+
"x".pack(:flv_signature) ===> "xFL"
|
101
|
+
|
102
|
+
Two shortcut names have special meanings: +default+ and +merge_all+. +default+ specifies the options to use when
|
103
|
+
nothing is specified, while +merge_all+ will be merged with all options. For example:
|
104
|
+
|
105
|
+
String.packers do |p|
|
106
|
+
p.set :merge_all, :fill => "*" # Unless explicitly specified, :fill will now be "*"
|
107
|
+
p.set :default, :bytes => 8 # If no option is given, this will act as default
|
108
|
+
end
|
109
|
+
|
110
|
+
"ab".pack ===> "ab******"
|
111
|
+
"ab".pack(:bytes=>4) ===> "ab**"
|
112
|
+
"ab".pack(:fill => "!") ===> "ab" # Not "ab!!"
|
113
|
+
|
114
|
+
A shortcut can refer to another shortcut, as so:
|
115
|
+
|
116
|
+
String.packers do |p|
|
117
|
+
p.set :creator, :bytes => 4
|
118
|
+
p.set :app_type, :creator
|
119
|
+
end
|
120
|
+
"hello".pack(:app_type) ===> "hell"
|
121
|
+
|
122
|
+
The following shortcuts and defaults are built-in the library:
|
123
|
+
|
124
|
+
=== Integer
|
125
|
+
:merge_all => :bytes=>4, :signed=>true, :endian=>:big
|
126
|
+
:default => :long
|
127
|
+
:long => {}
|
128
|
+
:short => :bytes=>2
|
129
|
+
:byte => :bytes=>1
|
130
|
+
:unsigned_long => :bytes=>4, :signed=>false
|
131
|
+
:unsigned_short => :bytes=>2, :signed=>false
|
132
|
+
|
133
|
+
=== Float
|
134
|
+
:merge_all => :precision => :single, :endian => :big
|
135
|
+
:default => :float
|
136
|
+
:double => :precision => :double
|
137
|
+
:float => {}
|
138
|
+
|
139
|
+
=== String
|
140
|
+
:merge_all => :fill => " "
|
141
|
+
|
142
|
+
== Files and StringIO
|
143
|
+
|
144
|
+
All IO objects (in particular files) can deal with packing easily. These examples will all return an array with 3 elements (a string, an integer and another string):
|
145
|
+
|
146
|
+
io >> :flv_signature >> Integer >> [String, {:bytes => 8}]
|
147
|
+
io.read(:flv_signature, Integer, [String, {:bytes => 8}])
|
148
|
+
io.read(:flv_signature, Integer, String, {:bytes => 8})
|
149
|
+
[io.read(:flv_signature), io.read(Integer), io.read(String, :bytes => 8)]
|
150
|
+
|
151
|
+
In a similar fashion, these have the same effect although the return value is different
|
152
|
+
|
153
|
+
io << "x".pack(:flv_signature) << 66.pack << "Hello".pack(:bytes => 8) # returns io
|
154
|
+
io << ["x", 66, "Hello"].pack(:flv_signature, {} , {:bytes => 8}) # returns io
|
155
|
+
io.write("x", :flv_signature, 66, "Hello", {:bytes => 8}) # returns the # of bytes written
|
156
|
+
io.packed << ["x",:flv_signature] << 66 << ["Hello", {:bytes => 8}] # returns a "packed io"
|
157
|
+
|
158
|
+
The last example shows how <tt>io.packed</tt> returns a special IO object (a packing IO) that will pack arguments before writing it.
|
159
|
+
This is to insure compatibility with the usual behavior of IO objects:
|
160
|
+
io << 66 ==> appends "66"
|
161
|
+
io.packed << 66 ==> appends "\000\000\000B"
|
162
|
+
|
163
|
+
We "cheated" in the previous example; instead of writing <tt>io.packed.write(...)</tt> we used the shorter form.
|
164
|
+
This works because we're passing more than one argument; for only one argument we must call <tt>io.packed.write(66)</tt>
|
165
|
+
less the usual +write+ method is called.
|
166
|
+
|
167
|
+
Since the standard library desn't define the <tt>>></tt> operator for IO objects, we are free to use either <tt>io.packed</tt> or <tt>io</tt> directly.
|
168
|
+
Note that reading one value only will return that value directly, not an array containing that value:
|
169
|
+
|
170
|
+
io.read(Integer) ===> 42, not [42]
|
171
|
+
io.read(Integer,Integer) ===> [42,43]
|
172
|
+
io << Integer ===> [42]
|
173
|
+
|
174
|
+
== Custom classes
|
175
|
+
|
176
|
+
Including the mixin +Packable+ will make a class (un)packable. Packable relies on +write_packed+
|
177
|
+
and unpacking on +read_packed+. For example:
|
178
|
+
|
179
|
+
class MyHeader < Struct.new(:signature, :nb_blocks)
|
180
|
+
include Packable
|
181
|
+
|
182
|
+
def write_packed(packedio, options)
|
183
|
+
packedio << [signature, {:bytes=>3}] << [nb_blocks, :short]
|
184
|
+
end
|
185
|
+
|
186
|
+
def self.read_packed(packedio, options)
|
187
|
+
h = MyHeader.new
|
188
|
+
h.signature, h.nb_blocks = packedio >> [String, {:bytes => 3}] >> :short
|
189
|
+
h
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
We used the argument name +packedio+ to remind us that these are packed IO objects, i.e.
|
194
|
+
they will write their arguments after packing them instead of converting them to string like normal IO objects.
|
195
|
+
With this definition, +MyHeader+ can be both packed and unpacked:
|
196
|
+
|
197
|
+
h = MyHeader.new("FLV", 65)
|
198
|
+
h.pack ===> "FLV\000A"
|
199
|
+
StringIO.new("FLV\000A") >> Signature ===> [a copy of h]
|
200
|
+
|
201
|
+
A default <tt>self.read_packed</tt> is provided by the +Packable+ mixin, which allows you to define +read_packed+ as
|
202
|
+
an instance method instead of a class method. In that case, +read_packed+ instance method is called with
|
203
|
+
the same arguments and should modify +self+ accordingly (instead of returning a new object).
|
204
|
+
It is not necessary to return +self+. The previous example can thus be shortened:
|
205
|
+
|
206
|
+
class MyHeader
|
207
|
+
#...
|
208
|
+
def read_packed(packedio, options)
|
209
|
+
self.signature, self.nb_blocks = packedio >> [String, {:bytes => 3}] >> :short
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
== Filter
|
214
|
+
|
215
|
+
Instead of writing a full-fledge class, sometimes it can be convenient to define a sort of wrapper we'll call filter. Here's an example:
|
216
|
+
|
217
|
+
String.packers.set :length_encoded do |packer|
|
218
|
+
packer.write { |packedio| packedio << length << self }
|
219
|
+
packer.read { |packedio| packedio.read(packedio.read(Integer)) }
|
220
|
+
end
|
221
|
+
|
222
|
+
"hello!".pack(:length_encoded) ===> "\000\000\000\006hello!"
|
223
|
+
["this", "is", "great!"].pack(*[:length_encoded]*3).unpack(*[:length_encoded]*3) ===> ["this", "is", "great!"]
|
224
|
+
|
225
|
+
Note that the +write+ block will be executed as an instance method (which is why we could use +length+ & +self+),
|
226
|
+
while +read+ is a normal block that must return the newly read object.
|
227
|
+
|
228
|
+
== Inheritance
|
229
|
+
|
230
|
+
A final note to say that packers are inherited in some way. For instance one could define a filter for all objects:
|
231
|
+
|
232
|
+
Object.packers.set :with_class do |packer|
|
233
|
+
packer.write { |io| io << [self.class.name, :length_encoded] << self }
|
234
|
+
packer.read do |io|
|
235
|
+
klass = eval(io.read(:length_encoded))
|
236
|
+
io.read(klass)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
[42, MyHeader.new("Wow", 1)].pack(:with_class, :with_class).unpack(:with_class, :with_class) ===> [42, MyHeader.new("Wow", 1)]
|
241
|
+
|
242
|
+
= License
|
243
|
+
|
244
|
+
packable is licensed under the terms of the (modified) BSD License, see the included LICENSE file.
|
245
|
+
|
246
|
+
Author:: Marc-André Lafortune
|
data/VERSION.yml
ADDED
data/lib/packable.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'backports'
|
3
|
+
require_relative 'packable/packers'
|
4
|
+
require_relative 'packable/mixin'
|
5
|
+
[Object, Array, String, Integer, Float, IO, Proc].each do |klass|
|
6
|
+
require_relative 'packable/extensions/' + klass.name.downcase
|
7
|
+
klass.class_eval { include Packable::Extensions.const_get(klass.name) }
|
8
|
+
end
|
9
|
+
StringIO.class_eval { include Packable::Extensions::IO } # Since StringIO doesn't inherit from IO
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Packable
|
4
|
+
module Extensions #:nodoc:
|
5
|
+
module Array #:nodoc:
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
alias_method_chain :pack, :long_form
|
9
|
+
include Packable
|
10
|
+
extend ClassMethods
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def pack_with_long_form(*arg)
|
15
|
+
return pack_without_long_form(*arg) if arg.first.is_a? String
|
16
|
+
pio = StringIO.new.packed
|
17
|
+
write_packed(pio, *arg)
|
18
|
+
pio.string
|
19
|
+
end
|
20
|
+
|
21
|
+
def write_packed(io, *how)
|
22
|
+
return io << self.original_pack(*how) if how.first.is_a? String
|
23
|
+
how = [:repeat => :all] if how.empty?
|
24
|
+
current = -1
|
25
|
+
how.each do |options|
|
26
|
+
repeat = options.is_a?(Hash) ? options.delete(:repeat) || 1 : 1
|
27
|
+
repeat = length - 1 - current if repeat == :all
|
28
|
+
repeat.times do
|
29
|
+
io.write(self[current+=1],options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods #:nodoc:
|
35
|
+
def read_packed(io, *how)
|
36
|
+
raise "Can't support builtin format for arrays" if (how.length == 1) && (how.first.is_a? String)
|
37
|
+
how.inject [] do |r, options|
|
38
|
+
repeat = options.is_a? Hash ? options.delete(:repeat) || 1 : 1
|
39
|
+
(0...repeat).inject r do
|
40
|
+
r << io.read(options)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Packable
|
2
|
+
module Extensions #:nodoc:
|
3
|
+
module Float #:nodoc:
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
include Packable
|
7
|
+
extend ClassMethods
|
8
|
+
packers do |p|
|
9
|
+
p.set :merge_all, :precision => :single, :endian => :big
|
10
|
+
p.set :double , :precision => :double
|
11
|
+
p.set :float , {}
|
12
|
+
p.set :default , :float
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def write_packed(io, options)
|
18
|
+
io << pack(self.class.pack_option_to_format(options))
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods #:nodoc:
|
22
|
+
def pack_option_to_format(options)
|
23
|
+
format = {:big => "G", :network => "G", :little => "E"}[options[:endian]]
|
24
|
+
format.downcase! if options[:precision] == :single
|
25
|
+
format
|
26
|
+
end
|
27
|
+
|
28
|
+
def read_packed(io, options)
|
29
|
+
io.read({:single => 4, :double => 8}[options[:precision]]) \
|
30
|
+
.unpack(pack_option_to_format(options)) \
|
31
|
+
.first
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Packable
|
2
|
+
module Extensions #:nodoc:
|
3
|
+
module Integer #:nodoc:
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
include Packable
|
7
|
+
extend ClassMethods
|
8
|
+
packers do |p|
|
9
|
+
p.set :merge_all , :bytes=>4, :signed=>true, :endian=>:big
|
10
|
+
p.set :default , :long
|
11
|
+
p.set :long , {}
|
12
|
+
p.set :short , :bytes=>2
|
13
|
+
p.set :char , :bytes=>1, :signed=>false
|
14
|
+
p.set :byte , :bytes=>1
|
15
|
+
p.set :unsigned_long , :bytes=>4, :signed=>false
|
16
|
+
p.set :unsigned_short , :bytes=>2, :signed=>false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def write_packed(io, options)
|
22
|
+
val = self
|
23
|
+
chars = (0...options[:bytes]).collect do
|
24
|
+
byte = val & 0xFF
|
25
|
+
val >>= 8
|
26
|
+
byte.chr
|
27
|
+
end
|
28
|
+
chars.reverse! unless options[:endian] == :little
|
29
|
+
io << chars.join
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods #:nodoc:
|
33
|
+
def unpack_string(s,options)
|
34
|
+
s = s.reverse if options[:endian] == :little
|
35
|
+
r = 0
|
36
|
+
s.each_byte {|b| r = (r << 8) + b}
|
37
|
+
r -= 1 << (8 * options[:bytes]) if options[:signed] && (1 == r >> (8 * options[:bytes] - 1))
|
38
|
+
r
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
Enumerator = Enumerable::Enumerator unless defined?(Enumerator)
|
3
|
+
|
4
|
+
module Packable
|
5
|
+
module Extensions #:nodoc:
|
6
|
+
module IO
|
7
|
+
def self.included(base) #:nodoc:
|
8
|
+
base.alias_method_chain :read, :packing
|
9
|
+
base.alias_method_chain :write, :packing
|
10
|
+
base.alias_method_chain :each, :packing
|
11
|
+
attr_accessor :throw_on_error
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the change in io.pos caused by the block.
|
15
|
+
# Has nothing to do with packing, but quite helpful and so simple...
|
16
|
+
def pos_change(&block)
|
17
|
+
delta =- pos
|
18
|
+
yield
|
19
|
+
delta += pos
|
20
|
+
end
|
21
|
+
|
22
|
+
# Usage:
|
23
|
+
# io >> Class
|
24
|
+
# io >> [Class, options]
|
25
|
+
# io >> :shortcut
|
26
|
+
def >> (options)
|
27
|
+
r = []
|
28
|
+
class << r
|
29
|
+
attr_accessor :stream
|
30
|
+
def >> (options)
|
31
|
+
self << stream.read(options)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
r.stream = self
|
35
|
+
r >> options
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns (or yields) a modified IO object that will always pack/unpack when writing/reading.
|
39
|
+
def packed
|
40
|
+
packedio = clone
|
41
|
+
packedio.set_encoding("ascii-8bit") if packedio.respond_to? :set_encoding
|
42
|
+
class << packedio
|
43
|
+
def << (arg)
|
44
|
+
arg = [arg, :default] unless arg.instance_of?(::Array)
|
45
|
+
pack_and_write(*arg)
|
46
|
+
self
|
47
|
+
end
|
48
|
+
def packed
|
49
|
+
block_given? ? yield(self) : self
|
50
|
+
end
|
51
|
+
alias_method :write, :pack_and_write #bypass test for argument length
|
52
|
+
end
|
53
|
+
block_given? ? yield(packedio) : packedio
|
54
|
+
end
|
55
|
+
|
56
|
+
def each_with_packing(*options, &block)
|
57
|
+
return each_without_packing(*options, &block) if (Integer === options.first) || (String === options.first)
|
58
|
+
return Enumerator.new(self, :each_with_packing, *options) unless block_given?
|
59
|
+
yield read(*options) until eof?
|
60
|
+
end
|
61
|
+
|
62
|
+
def write_with_packing(*arg)
|
63
|
+
(arg.length == 1) ? write_without_packing(*arg) : pack_and_write(*arg)
|
64
|
+
end
|
65
|
+
|
66
|
+
def read_with_packing(*arg)
|
67
|
+
return read_without_packing(*arg) if (arg.length == 0) || (arg.first.is_a?(Numeric) && (arg.length == 1))
|
68
|
+
values = Packable::Packers.to_class_option_list(*arg).map do |klass, options, original|
|
69
|
+
if eof?
|
70
|
+
raise EOFError, "End of IO when attempting to read #{klass} with options #{original.inspect}" if @throw_on_eof
|
71
|
+
nil
|
72
|
+
elsif options[:read_packed]
|
73
|
+
options[:read_packed].call(self)
|
74
|
+
else
|
75
|
+
klass.read_packed(self, options)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
return values.size > 1 ? values : values.first
|
79
|
+
end
|
80
|
+
|
81
|
+
def pack_and_write(*arg)
|
82
|
+
original_pos = pos
|
83
|
+
Packable::Packers.to_object_option_list(*arg).each do |obj, options|
|
84
|
+
if options[:write_packed]
|
85
|
+
options[:write_packed].bind(obj).call(self)
|
86
|
+
else
|
87
|
+
obj.write_packed(self, options)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
pos - original_pos
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Packable
|
2
|
+
module Extensions #:nodoc:
|
3
|
+
module Object #:nodoc:
|
4
|
+
def self.included(base) #:nodoc:
|
5
|
+
base.class_eval do
|
6
|
+
class << self
|
7
|
+
# include only packers method into Object
|
8
|
+
include PackersClassMethod
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Packable
|
2
|
+
module Extensions #:nodoc:
|
3
|
+
module Proc
|
4
|
+
# A bit of wizardry to return an +UnboundMethod+ which can be bound to any object
|
5
|
+
def unbind
|
6
|
+
Object.send(:define_method, :__temp_bound_method, &self)
|
7
|
+
Object.instance_method(:__temp_bound_method)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Shortcut for <tt>unbind.bind(to)</tt>
|
11
|
+
def bind(to)
|
12
|
+
unbind.bind(to)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Packable
|
4
|
+
module Extensions #:nodoc:
|
5
|
+
module String #:nodoc:
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.class_eval do
|
9
|
+
include Packable
|
10
|
+
extend ClassMethods
|
11
|
+
alias_method_chain :unpack, :long_form
|
12
|
+
packers.set :merge_all, :fill => " "
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def write_packed(io, options)
|
17
|
+
return io.write_without_packing(self) unless options[:bytes]
|
18
|
+
io.write_without_packing(self[0...options[:bytes]].ljust(options[:bytes], options[:fill] || "\000"))
|
19
|
+
end
|
20
|
+
|
21
|
+
def unpack_with_long_form(*arg)
|
22
|
+
return unpack_without_long_form(*arg) if arg.first.is_a? String
|
23
|
+
StringIO.new(self).packed.read(*arg)
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods #:nodoc:
|
27
|
+
def unpack_string(s, options)
|
28
|
+
s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# The Packable mixin itself...
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module Packable
|
6
|
+
def self.included(base) #:nodoc:
|
7
|
+
base.class_eval do
|
8
|
+
class << self
|
9
|
+
include PackersClassMethod
|
10
|
+
include ClassMethods
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# +options+ can be a Hash, a shortcut (Symbol) or a String (old-style)
|
16
|
+
def pack(options = :default)
|
17
|
+
return [self].pack(options) if options.is_a? String
|
18
|
+
(StringIO.new.packed << [self, options]).string
|
19
|
+
end
|
20
|
+
|
21
|
+
module PackersClassMethod
|
22
|
+
# Returns or yields a the Packers.for(class)
|
23
|
+
# Normal use is packers.set ...
|
24
|
+
# (see docs or Packers::set for usage)
|
25
|
+
def packers
|
26
|
+
yield packers if block_given?
|
27
|
+
Packers.for(self)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def unpack(s, options = :default)
|
33
|
+
return s.unpack(options).first if options.is_a? String
|
34
|
+
StringIO.new(s).packed.read(self, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Default +read_packed+ calls either the instance method <tt>read_packed</tt> or the
|
38
|
+
# class method +unpack_string+. Choose:
|
39
|
+
# * define a class method +read_packed+ that returns the newly read object
|
40
|
+
# * define an instance method +read_packed+ which reads the io into +self+
|
41
|
+
# * define a class method +unpack_string+ that reads and returns an object from the string. In this case, options[:bytes] should be specified!
|
42
|
+
def read_packed(io, options)
|
43
|
+
if method_defined? :read_packed
|
44
|
+
mandatory = instance_method(:initialize).arity
|
45
|
+
mandatory = -1-mandatory if mandatory < 0
|
46
|
+
obj = new(*[nil]*mandatory)
|
47
|
+
obj.read_packed(io, options)
|
48
|
+
obj
|
49
|
+
else
|
50
|
+
len = options[:bytes]
|
51
|
+
s = len ? io.read(len) : io.read
|
52
|
+
unpack_string(s, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Packable
|
2
|
+
|
3
|
+
# Packers for any packable class.
|
4
|
+
class Packers < Hash
|
5
|
+
SPECIAL = [:default, :merge_all].freeze
|
6
|
+
|
7
|
+
# Usage:
|
8
|
+
# PackableClass.packers.set :shortcut, :option => value, ...
|
9
|
+
# PackableClass.packers { |p| p.set...; p.set... }
|
10
|
+
# PackableClass.packers.set :shortcut, :another_shortcut
|
11
|
+
# PackableClass.packers.set :shortcut do |packer|
|
12
|
+
# packer.write{|io| io << self.something... }
|
13
|
+
# packer.read{|io| Whatever.new(io.read(...)) }
|
14
|
+
# end
|
15
|
+
def set(key, options_or_shortcut={})
|
16
|
+
if block_given?
|
17
|
+
packer = FilterCapture.new options_or_shortcut
|
18
|
+
yield packer
|
19
|
+
end
|
20
|
+
self[key] = options_or_shortcut
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(klass) #:nodoc:
|
25
|
+
@klass = klass
|
26
|
+
end
|
27
|
+
|
28
|
+
def lookup(key) #:nodoc:
|
29
|
+
k = @klass
|
30
|
+
begin
|
31
|
+
if found = Packers.for(k)[key]
|
32
|
+
return found
|
33
|
+
end
|
34
|
+
k = k.superclass
|
35
|
+
end while k
|
36
|
+
SPECIAL.include?(key) ? {} : raise("Unknown option #{key} for #{@klass}")
|
37
|
+
end
|
38
|
+
|
39
|
+
def finalize(options) #:nodoc:
|
40
|
+
options = lookup(options) while options.is_a? Symbol
|
41
|
+
lookup(:merge_all).merge(options)
|
42
|
+
end
|
43
|
+
|
44
|
+
@@packers_for_class = Hash.new{|h, klass| h[klass] = Packers.new(klass)}
|
45
|
+
|
46
|
+
# Returns the configuration for the given +klass+.
|
47
|
+
def self.for(klass)
|
48
|
+
@@packers_for_class[klass]
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.to_class_option_list(*arg) #:nodoc:
|
52
|
+
r = []
|
53
|
+
until arg.empty? do
|
54
|
+
k, options = original = arg.shift
|
55
|
+
k, options = global_lookup(k) if k.is_a? Symbol
|
56
|
+
raise TypeError, "Expected a class or symbol: #{k.inspect}" unless k.instance_of? Class
|
57
|
+
options ||= arg.first.is_a?(Hash) ? arg.shift.tap{|o| original = [original, o]} : :default
|
58
|
+
r << [k, k.packers.finalize(options), original]
|
59
|
+
end
|
60
|
+
r
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.to_object_option_list(*arg) #:nodoc:
|
64
|
+
r=[]
|
65
|
+
until arg.empty? do
|
66
|
+
obj = arg.shift
|
67
|
+
options = case arg.first
|
68
|
+
when Hash, Symbol
|
69
|
+
arg.shift
|
70
|
+
else
|
71
|
+
:default
|
72
|
+
end
|
73
|
+
r << [obj, obj.class.packers.finalize(options)]
|
74
|
+
end
|
75
|
+
r
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def self.global_lookup(key) #:nodoc:
|
80
|
+
@@packers_for_class.each do |klass, packers|
|
81
|
+
if options = packers[key]
|
82
|
+
return [klass, options]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
raise "Couldn't find packing option #{key}"
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
# Use to capture the blocks given to read/write
|
92
|
+
class FilterCapture #:nodoc:
|
93
|
+
attr_accessor :options
|
94
|
+
def initialize(options)
|
95
|
+
self.options = options
|
96
|
+
end
|
97
|
+
|
98
|
+
def read(&block)
|
99
|
+
options[:read_packed] = block
|
100
|
+
end
|
101
|
+
|
102
|
+
def write(&block)
|
103
|
+
options[:write_packed] = block.unbind
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
# Warning: ugly...
|
3
|
+
class MyHeader < Struct.new(:signature, :nb_blocks)
|
4
|
+
include Packable
|
5
|
+
|
6
|
+
def write_packed(packedio, options)
|
7
|
+
packedio << [signature, {:bytes=>3}] << [nb_blocks, :short]
|
8
|
+
end
|
9
|
+
|
10
|
+
def read_packed(packedio, options)
|
11
|
+
self.signature, self.nb_blocks = packedio >> [String, {:bytes => 3}] >> :short
|
12
|
+
end
|
13
|
+
|
14
|
+
def ohoh
|
15
|
+
:ahah
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
class PackableDocTest < Test::Unit::TestCase
|
21
|
+
def test_doc
|
22
|
+
|
23
|
+
assert_equal [1,2,3], StringIO.new("\000\001\000\002\000\003").each(:short).to_a
|
24
|
+
|
25
|
+
|
26
|
+
String.packers.set :flv_signature, :bytes => 3, :fill => "FLV"
|
27
|
+
|
28
|
+
assert_equal "xFL", "x".pack(:flv_signature)
|
29
|
+
|
30
|
+
String.packers do |p|
|
31
|
+
p.set :merge_all, :fill => "*" # Unless explicitly specified, :fill will now be "*"
|
32
|
+
p.set :default, :bytes => 8 # If no option is given, this will act as default
|
33
|
+
end
|
34
|
+
|
35
|
+
assert_equal "ab******", "ab".pack
|
36
|
+
assert_equal "ab**", "ab".pack(:bytes=>4)
|
37
|
+
assert_equal "ab", "ab".pack(:fill => "!")
|
38
|
+
assert_equal "ab!!", "ab".pack(:fill => "!", :bytes => 4)
|
39
|
+
|
40
|
+
String.packers do |p|
|
41
|
+
p.set :creator, :bytes => 4
|
42
|
+
p.set :app_type, :creator
|
43
|
+
p.set :default, {} # Reset to a sensible default...
|
44
|
+
p.set :merge_all, :fill => " "
|
45
|
+
p.set :eigth_bytes, :bytes => 8
|
46
|
+
end
|
47
|
+
|
48
|
+
assert_equal "hello".pack(:app_type), "hell"
|
49
|
+
|
50
|
+
assert_equal [["sig", 1, "hello, w"]]*4,
|
51
|
+
[
|
52
|
+
lambda { |io| io >> :flv_signature >> Integer >> [String, {:bytes => 8}] },
|
53
|
+
lambda { |io| io.read(:flv_signature, Integer, [String, {:bytes => 8}]) },
|
54
|
+
lambda { |io| io.read(:flv_signature, Integer, String, {:bytes => 8}) },
|
55
|
+
lambda { |io| [io.read(:flv_signature), io.read(Integer), io.read(String, {:bytes => 8})] }
|
56
|
+
].map {|proc| proc.call(StringIO.new("sig\000\000\000\001hello, world"))}
|
57
|
+
|
58
|
+
|
59
|
+
ex = "xFL\000\000\000BHello "
|
60
|
+
[
|
61
|
+
lambda { |io| io << "x".pack(:flv_signature) << 66.pack << "Hello".pack(:bytes => 8)}, # returns io
|
62
|
+
lambda { |io| io << ["x", 66, "Hello"].pack(:flv_signature, :default , {:bytes => 8})}, # returns io
|
63
|
+
lambda { |io| io.write("x", :flv_signature, 66, "Hello", {:bytes => 8}) }, # returns the # of bytes written
|
64
|
+
lambda { |io| io.packed << ["x",:flv_signature] << 66 << ["Hello", {:bytes => 8}] } # returns io.packed
|
65
|
+
].zip([StringIO, StringIO, ex.length, StringIO.new.packed.class]) do |proc, compare|
|
66
|
+
ios = StringIO.new
|
67
|
+
assert_operator compare, :===, proc.call(ios)
|
68
|
+
ios.rewind
|
69
|
+
assert_equal ex, ios.read, "With #{proc}"
|
70
|
+
end
|
71
|
+
|
72
|
+
#insure StringIO class is not affected
|
73
|
+
ios = StringIO.new
|
74
|
+
ios.packed
|
75
|
+
ios << 66
|
76
|
+
ios.rewind
|
77
|
+
assert_equal "66", ios.read
|
78
|
+
|
79
|
+
|
80
|
+
String.packers.set :length_encoded do |packer|
|
81
|
+
packer.write { |io| io << length << self }
|
82
|
+
packer.read { |io| io.read(io.read(Integer)) }
|
83
|
+
end
|
84
|
+
|
85
|
+
assert_equal "\000\000\000\006hello!", "hello!".pack(:length_encoded)
|
86
|
+
assert_equal ["this", "is", "great!"], ["this", "is", "great!"].pack(*[:length_encoded]*3).unpack(*[:length_encoded]*3)
|
87
|
+
|
88
|
+
h = MyHeader.new("FLV", 65)
|
89
|
+
assert_equal "FLV\000A", h.pack
|
90
|
+
h2, = StringIO.new("FLV\000A") >> MyHeader
|
91
|
+
assert_equal h, h2
|
92
|
+
assert_equal h.ohoh, h2.ohoh
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
Object.packers.set :with_class do |packer|
|
97
|
+
packer.write { |io| io << [self.class.name, :length_encoded] << self }
|
98
|
+
packer.read do |io|
|
99
|
+
klass = eval(io.read(:length_encoded))
|
100
|
+
io.read(klass)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
ar = [42, MyHeader.new("FLV", 65)]
|
104
|
+
assert_equal ar, ar.pack(:with_class, :with_class).unpack(:with_class, :with_class)
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
# Warning: ugly...
|
3
|
+
|
4
|
+
class XYZ
|
5
|
+
include Packable
|
6
|
+
def write_packed(io, options)
|
7
|
+
io << "xyz"
|
8
|
+
end
|
9
|
+
def self.unpack_string(s, options)
|
10
|
+
raise "baddly packed XYZ: #{s}" unless "xyz" == s
|
11
|
+
XYZ.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class TestingPack < Test::Unit::TestCase
|
16
|
+
|
17
|
+
context "Original form" do
|
18
|
+
should "pack like before" do
|
19
|
+
assert_equal "a \000\000\000\001", ["a",1,66].pack("A3N")
|
20
|
+
end
|
21
|
+
|
22
|
+
should "be equivalent to new form" do
|
23
|
+
assert_equal ["a",1,2.34, 66].pack({:bytes=>3}, {:bytes=>4, :endian=>:big}, {:precision=>:double, :endian=>:big}), ["a",1,2.34, 66].pack("A3NG")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_shortcuts
|
28
|
+
assert_equal 0x123456.pack(:short), 0x123456.pack(:bytes => 2)
|
29
|
+
assert_equal 0x3456, 0x123456.pack(:short).unpack(:short)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_custom_form
|
33
|
+
assert_equal "xyz", XYZ.new.pack
|
34
|
+
assert_equal XYZ, "xyz".unpack(XYZ).class
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_pack_default
|
38
|
+
assert_equal "\000\000\000\006", 6.pack
|
39
|
+
assert_equal "abcd", "abcd".pack
|
40
|
+
assert_equal "\000\000\000\006abcd", [6,"abcd"].pack
|
41
|
+
String.packers.set :flv_signature, :bytes => 3, :fill => "FLV"
|
42
|
+
assert_equal "xFL", "x".pack(:flv_signature)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_integer
|
46
|
+
assert_equal "\002\001\000", 258.pack(:bytes => 3, :endian => :little)
|
47
|
+
assert_equal 258, Integer.unpack("\002\001\000", :bytes => 3, :endian => :little)
|
48
|
+
assert_equal (1<<24)-1, -1.pack(:bytes => 3).unpack(Integer, :bytes => 3, :signed => false)
|
49
|
+
assert_equal -1, -1.pack(:bytes => 3).unpack(Integer, :bytes => 3, :signed => true)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_io
|
53
|
+
io = StringIO.new("\000\000\000\006abcdE!")
|
54
|
+
n, s, c = io >> [Fixnum, {:signed=>false}] >> [String, {:bytes => 4}] >> :char
|
55
|
+
assert_equal 6, n
|
56
|
+
assert_equal "abcd", s
|
57
|
+
assert_equal 69, c
|
58
|
+
assert_equal "!", io.read
|
59
|
+
end
|
60
|
+
|
61
|
+
should "do basic type checking" do
|
62
|
+
assert_raise(TypeError) {"".unpack(42, :short)}
|
63
|
+
end
|
64
|
+
|
65
|
+
context "Filters" do
|
66
|
+
context "for Object" do
|
67
|
+
Object.packers.set :generic_class_writer do |packer|
|
68
|
+
packer.write do |io|
|
69
|
+
io << self.class.name << self
|
70
|
+
end
|
71
|
+
end
|
72
|
+
should "be follow accessible everywhere" do
|
73
|
+
assert_equal "StringHello", "Hello".pack(:generic_class_writer)
|
74
|
+
assert_equal "Fixnum\000\000\000\006", 6.pack(:generic_class_writer)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
context "for a specific class" do
|
78
|
+
String.packers.set :specific_writer do |packer|
|
79
|
+
packer.write do |io|
|
80
|
+
io << "Hello"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
should "be accessible only from that class and descendants" do
|
85
|
+
assert_equal "Hello", "World".pack(:specific_writer)
|
86
|
+
assert_raise RuntimeError do
|
87
|
+
6.pack(:specific_writer)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: packable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Marc-Andr\xC3\xA9 Lafortune"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-03 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: marcandre-backports
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: If you need to do read and write binary data, there is of course <Array::pack and String::unpack The packable library makes (un)packing nicer, smarter and more powerful.
|
26
|
+
email: github@marc-andre.ca
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.rdoc
|
33
|
+
- LICENSE
|
34
|
+
files:
|
35
|
+
- CHANGELOG.rdoc
|
36
|
+
- README.rdoc
|
37
|
+
- VERSION.yml
|
38
|
+
- lib/packable
|
39
|
+
- lib/packable/extensions
|
40
|
+
- lib/packable/extensions/array.rb
|
41
|
+
- lib/packable/extensions/float.rb
|
42
|
+
- lib/packable/extensions/integer.rb
|
43
|
+
- lib/packable/extensions/io.rb
|
44
|
+
- lib/packable/extensions/object.rb
|
45
|
+
- lib/packable/extensions/proc.rb
|
46
|
+
- lib/packable/extensions/string.rb
|
47
|
+
- lib/packable/mixin.rb
|
48
|
+
- lib/packable/packers.rb
|
49
|
+
- lib/packable.rb
|
50
|
+
- test/packing_doc_test.rb
|
51
|
+
- test/packing_test.rb
|
52
|
+
- test/test_helper.rb
|
53
|
+
- LICENSE
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://github.com/marcandre/packable
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --title
|
59
|
+
- Packable library
|
60
|
+
- --main
|
61
|
+
- README.rdoc
|
62
|
+
- --line-numbers
|
63
|
+
- --inline-source
|
64
|
+
- --inline-source
|
65
|
+
- --charset=UTF-8
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
version:
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project: marcandre
|
83
|
+
rubygems_version: 1.3.1
|
84
|
+
signing_key:
|
85
|
+
specification_version: 2
|
86
|
+
summary: Extensive packing and unpacking capabilities
|
87
|
+
test_files: []
|
88
|
+
|