packable 1.2.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/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
|
+
|