packable-1.3.6/ 0000755 0000041 0000041 00000000000 12202633725 013315 5 ustar www-data www-data packable-1.3.6/test/ 0000755 0000041 0000041 00000000000 12202633725 014274 5 ustar www-data www-data packable-1.3.6/test/test_helper.rb 0000644 0000041 0000041 00000000232 12202633725 017134 0 ustar www-data www-data require 'rubygems'
require 'test/unit'
require 'shoulda'
require 'mocha'
require File.dirname(__FILE__)+'/../lib/packable'
class Test::Unit::TestCase
end packable-1.3.6/test/packing_doc_test.rb 0000644 0000041 0000041 00000007243 12202633725 020127 0 ustar www-data www-data require File.expand_path(File.dirname(__FILE__) + '/test_helper')
# Warning: ugly...
class MyHeader < Struct.new(:signature, :nb_blocks)
include Packable
def write_packed(packedio, options)
packedio << [signature, {:bytes=>3}] << [nb_blocks, :short]
end
def read_packed(packedio, options)
self.signature, self.nb_blocks = packedio >> [String, {:bytes => 3}] >> :short
end
def ohoh
:ahah
end
end
class PackableDocTest < Test::Unit::TestCase
def test_doc
assert_equal [1,2,3], StringIO.new("\000\001\000\002\000\003").each(:short).to_a
String.packers.set :flv_signature, :bytes => 3, :fill => "FLV"
assert_equal "xFL", "x".pack(:flv_signature)
String.packers do |p|
p.set :merge_all, :fill => "*" # Unless explicitly specified, :fill will now be "*"
p.set :default, :bytes => 8 # If no option is given, this will act as default
end
assert_equal "ab******", "ab".pack
assert_equal "ab**", "ab".pack(:bytes=>4)
assert_equal "ab", "ab".pack(:fill => "!")
assert_equal "ab!!", "ab".pack(:fill => "!", :bytes => 4)
String.packers do |p|
p.set :creator, :bytes => 4
p.set :app_type, :creator
p.set :default, {} # Reset to a sensible default...
p.set :merge_all, :fill => " "
p.set :eigth_bytes, :bytes => 8
end
assert_equal "hello".pack(:app_type), "hell"
assert_equal [["sig", 1, "hello, w"]]*4,
[
lambda { |io| io >> :flv_signature >> Integer >> [String, {:bytes => 8}] },
lambda { |io| io.read(:flv_signature, Integer, [String, {:bytes => 8}]) },
lambda { |io| io.read(:flv_signature, Integer, String, {:bytes => 8}) },
lambda { |io| [io.read(:flv_signature), io.read(Integer), io.read(String, {:bytes => 8})] }
].map {|proc| proc.call(StringIO.new("sig\000\000\000\001hello, world"))}
ex = "xFL\000\000\000BHello "
[
lambda { |io| io << "x".pack(:flv_signature) << 66.pack << "Hello".pack(:bytes => 8)}, # returns io
lambda { |io| io << ["x", 66, "Hello"].pack(:flv_signature, :default , {:bytes => 8})}, # returns io
lambda { |io| io.write("x", :flv_signature, 66, "Hello", {:bytes => 8}) }, # returns the # of bytes written
lambda { |io| io.packed << ["x",:flv_signature] << 66 << ["Hello", {:bytes => 8}] } # returns io.packed
].zip([StringIO, StringIO, ex.length, StringIO.new.packed.class]) do |proc, compare|
ios = StringIO.new
assert_operator compare, :===, proc.call(ios)
ios.rewind
assert_equal ex, ios.read, "With #{proc}"
end
#insure StringIO class is not affected
ios = StringIO.new
ios.packed
ios << 66
ios.rewind
assert_equal "66", ios.read
String.packers.set :length_encoded do |packer|
packer.write { |io| io << length << self }
packer.read { |io| io.read(io.read(Integer)) }
end
assert_equal "\000\000\000\006hello!", "hello!".pack(:length_encoded)
assert_equal ["this", "is", "great!"], ["this", "is", "great!"].pack(*[:length_encoded]*3).unpack(*[:length_encoded]*3)
h = MyHeader.new("FLV", 65)
assert_equal "FLV\000A", h.pack
h2, = StringIO.new("FLV\000A") >> MyHeader
assert_equal h, h2
assert_equal h.ohoh, h2.ohoh
Object.packers.set :with_class do |packer|
packer.write { |io| io << [self.class.name, :length_encoded] << self }
packer.read do |io|
klass = eval(io.read(:length_encoded))
io.read(klass)
end
end
ar = [42, MyHeader.new("FLV", 65)]
assert_equal ar, ar.pack(:with_class, :with_class).unpack(:with_class, :with_class)
end
end packable-1.3.6/test/packing_test.rb 0000644 0000041 0000041 00000010250 12202633725 017272 0 ustar www-data www-data require File.expand_path(File.dirname(__FILE__) + '/test_helper')
# Warning: ugly...
class XYZ
include Packable
def write_packed(io, options)
io << "xyz"
end
def self.unpack_string(s, options)
raise "baddly packed XYZ: #{s}" unless "xyz" == s
XYZ.new
end
end
class TestingPack < Test::Unit::TestCase
context "Original form" do
should "pack like before" do
assert_equal "a \000\000\000\001", ["a",1,66].pack("A3N")
end
should "be equivalent to new form" do
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")
end
end
def test_shortcuts
assert_equal 0x123456.pack(:short), 0x123456.pack(:bytes => 2)
assert_equal 0x3456, 0x123456.pack(:short).unpack(:short)
end
def test_custom_form
assert_equal "xyz", XYZ.new.pack
assert_equal XYZ, "xyz".unpack(XYZ).class
end
def test_pack_default
assert_equal "\000\000\000\006", 6.pack
assert_equal "abcd", "abcd".pack
assert_equal "\000\000\000\006abcd", [6,"abcd"].pack
String.packers.set :flv_signature, :bytes => 3, :fill => "FLV"
assert_equal "xFL", "x".pack(:flv_signature)
end
def test_integer
assert_equal "\002\001\000", 258.pack(:bytes => 3, :endian => :little)
assert_equal 258, Integer.unpack("\002\001\000", :bytes => 3, :endian => :little)
assert_equal (1<<24)-1, -1.pack(:bytes => 3).unpack(Integer, :bytes => 3, :signed => false)
assert_equal -1, -1.pack(:bytes => 3).unpack(Integer, :bytes => 3, :signed => true)
assert_equal 42, 42.pack('L').unpack(Integer, :bytes => 4, :endian => :native)
assert_raise(ArgumentError){ 42.pack(:endian => "Geronimo")}
end
def test_bignum
assert_equal 1.pack(:long), ((1 << 69) + 1).pack(:long)
assert_equal "*" + ("\000" * 15), (42 << (8*15)).pack(:bytes => 16)
assert_equal 42 << (8*15), (42 << (8*15)).pack(:bytes => 16).unpack(Integer, :bytes => 16)
assert_equal 42 << (8*15), (42 << (8*15)).pack(:bytes => 16).unpack(Bignum, :bytes => 16)
end
def test_float
assert_raise(ArgumentError){ Math::PI.pack(:endian => "Geronimo")}
assert_equal Math::PI, Math::PI.pack(:precision => :double, :endian => :native).unpack(Float, :precision => :double, :endian => :native)
# Issue #1
assert_equal Math::PI.pack(:precision => :double), Math::PI.pack('G')
assert_equal Math::PI.pack(:precision => :single), Math::PI.pack('g')
assert_equal Math::PI.pack(:precision => :double), Math::PI.pack('G')
end
def test_io
io = StringIO.new("\000\000\000\006abcdE!")
n, s, c = io >> [Fixnum, {:signed=>false}] >> [String, {:bytes => 4}] >> :char
assert_equal 6, n
assert_equal "abcd", s
assert_equal 69, c
assert_equal "!", io.read
end
should "do basic type checking" do
assert_raise(TypeError) {"".unpack(42, :short)}
end
context "Reading beyond the eof" do
should "raises an EOFError when reading" do
["", "x"].each do |s|
io = StringIO.new(s)
assert_raise(EOFError) {io.read(:double)}
assert_raise(EOFError) {io.read(:short)}
assert_raise(EOFError) {io.read(String, :bytes => 4)}
end
end
should "return nil for unpacking" do
assert_nil "".unpack(:double)
assert_nil "".unpack(:short)
assert_nil "x".unpack(:double)
assert_nil "x".unpack(:short)
end
end
context "Filters" do
context "for Object" do
Object.packers.set :generic_class_writer do |packer|
packer.write do |io|
io << self.class.name << self
end
end
should "be follow accessible everywhere" do
assert_equal "StringHello", "Hello".pack(:generic_class_writer)
assert_equal "Fixnum\000\000\000\006", 6.pack(:generic_class_writer)
end
end
context "for a specific class" do
String.packers.set :specific_writer do |packer|
packer.write do |io|
io << "Hello"
end
end
should "be accessible only from that class and descendants" do
assert_equal "Hello", "World".pack(:specific_writer)
assert_raise RuntimeError do
6.pack(:specific_writer)
end
end
end
end
end
packable-1.3.6/README.rdoc 0000644 0000041 0000041 00000023261 12202633725 015127 0 ustar www-data www-data = Packable Library - Intro
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.
In case you are wondering why on earth someone would want to do serious (un)packing when YAML & XML are built-in:
I wrote this library to read and write FLV files...
== Feature summary:
=== Explicit forms
Strings, integers & floats have long forms instead of the cryptic letter notation. For example:
["answer", 42].pack("C3n")
can be written as:
["answer", 42].pack({:bytes => 3}, {:bytes => 2, :endian => :big})
This can look a bit too verbose, so let's introduce shortcuts right away:
=== Shortcuts
Most commonly used options have shortcuts and you can define your own. For example:
:unsigned_long <===> {:bytes => 4, :signed => false, :endian => :big}
=== IO
IO classes (File & StringIO) can use (un)packing routines.
For example:
signature, block_len, temperature = my_file >> [String, :bytes=>3] >> Integer >> :float
The method +each+ also accepts packing options:
StringIO.new("\000\001\000\002\000\003").each(:short).to_a ===> [1,2,3]
=== Custom classes
It's easy to make you own classes (un)packable. All the previous goodies are thus available:
File.open("great_flick.flv") do |f|
head = f.read(FLV::Header)
f.each(FLV::Tag) do |tag|
# do something meaningful with each tag...
end
end
=== Filters
It's also easy to define special shortcuts that will call blocks to (un)pack any class.
As an example, this could be useful to add special packing features to String (without monkey patching String::pack).
== Installation
First, ensure that you're running at least RubyGems 1.2 (check gem --version if you're not sure -- to update: sudo gem update --system).
Add GitHub to your gem sources (if you haven't already):
sudo gem sources -a http://gems.github.com
Get the gem:
sudo gem install marcandre-packable
That's it! Simply require 'packable' in your code to use it.
== Compatibility
Designed to work with ruby 1.8 & 1.9.
= Documentation
== Packing and unpacking
The library was designed to be backward compatible, so the usual packing and unpacking methods still work as before.
All packable objects can also be packed directly (no need to use an array). For example:
42.pack("n") ===> "\000*"
In a similar fashion, unpacking can done using class methods:
Integer.unpack("\000*", "n") ===> 42
== Formats
Although the standard string formats can still be used, it is possible to pass a list of options (see example in feature summary).
These are the options for core types:
=== Integer
[+bytes+] Number of bytes (default is 4) to use.
[+endian+] Either :big (or :network, default), :little or :native.
[+signed+] Either +true+ (default) or +false+. This will make a difference only when unpacking.
=== Float
[+precision+] Either :single (default) or :double.
[+endian+] Either :big (or :network, default), :little or :native.
=== String
[+bytes+] Total length (default is the full length)
[+fill+] The string to use for filling when packing a string shorter than the specified bytes option. Default is a space.
=== Array
[+repeat+] This option can be used (when packing only) to repeat the current option. A value of :all will mean for all remaining elements of the array.
When unpacking, it is necessary to specify the class in addition to any option, like so:
"AB".unpack(Integer, :bytes => 2, :endian => :big, :signed => false) ===> 0x3132
== Shortcuts and default values
It's easy to add shortcuts for easier (un)packing:
String.packers.set :flv_signature, :bytes => 3, :fill => "FLV"
"x".pack(:flv_signature) ===> "xFL"
Two shortcut names have special meanings: +default+ and +merge_all+. +default+ specifies the options to use when
nothing is specified, while +merge_all+ will be merged with all options. For example:
String.packers do |p|
p.set :merge_all, :fill => "*" # Unless explicitly specified, :fill will now be "*"
p.set :default, :bytes => 8 # If no option is given, this will act as default
end
"ab".pack ===> "ab******"
"ab".pack(:bytes=>4) ===> "ab**"
"ab".pack(:fill => "!") ===> "ab" # Not "ab!!"
A shortcut can refer to another shortcut, as so:
String.packers do |p|
p.set :creator, :bytes => 4
p.set :app_type, :creator
end
"hello".pack(:app_type) ===> "hell"
The following shortcuts and defaults are built-in the library:
=== Integer
:merge_all => :bytes=>4, :signed=>true, :endian=>:big
:default => :long
:long => {}
:short => :bytes=>2
:byte => :bytes=>1
:unsigned_long => :bytes=>4, :signed=>false
:unsigned_short => :bytes=>2, :signed=>false
=== Float
:merge_all => :precision => :single, :endian => :big
:default => :float
:double => :precision => :double
:float => {}
=== String
:merge_all => :fill => " "
== Files and StringIO
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):
io >> :flv_signature >> Integer >> [String, {:bytes => 8}]
io.read(:flv_signature, Integer, [String, {:bytes => 8}])
io.read(:flv_signature, Integer, String, {:bytes => 8})
[io.read(:flv_signature), io.read(Integer), io.read(String, :bytes => 8)]
In a similar fashion, these have the same effect although the return value is different
io << "x".pack(:flv_signature) << 66.pack << "Hello".pack(:bytes => 8) # returns io
io << ["x", 66, "Hello"].pack(:flv_signature, {} , {:bytes => 8}) # returns io
io.write("x", :flv_signature, 66, "Hello", {:bytes => 8}) # returns the # of bytes written
io.packed << ["x",:flv_signature] << 66 << ["Hello", {:bytes => 8}] # returns a "packed io"
The last example shows how io.packed returns a special IO object (a packing IO) that will pack arguments before writing it.
This is to insure compatibility with the usual behavior of IO objects:
io << 66 ==> appends "66"
io.packed << 66 ==> appends "\000\000\000B"
We "cheated" in the previous example; instead of writing io.packed.write(...) we used the shorter form.
This works because we're passing more than one argument; for only one argument we must call io.packed.write(66)
less the usual +write+ method is called.
Since the standard library desn't define the >> operator for IO objects, we are free to use either io.packed or io directly.
Note that reading one value only will return that value directly, not an array containing that value:
io.read(Integer) ===> 42, not [42]
io.read(Integer,Integer) ===> [42,43]
io << Integer ===> [42]
== Custom classes
Including the mixin +Packable+ will make a class (un)packable. Packable relies on +write_packed+
and unpacking on +read_packed+. For example:
class MyHeader < Struct.new(:signature, :nb_blocks)
include Packable
def write_packed(packedio, options)
packedio << [signature, {:bytes=>3}] << [nb_blocks, :short]
end
def self.read_packed(packedio, options)
h = MyHeader.new
h.signature, h.nb_blocks = packedio >> [String, {:bytes => 3}] >> :short
h
end
end
We used the argument name +packedio+ to remind us that these are packed IO objects, i.e.
they will write their arguments after packing them instead of converting them to string like normal IO objects.
With this definition, +MyHeader+ can be both packed and unpacked:
h = MyHeader.new("FLV", 65)
h.pack ===> "FLV\000A"
StringIO.new("FLV\000A") >> Signature ===> [a copy of h]
A default self.read_packed is provided by the +Packable+ mixin, which allows you to define +read_packed+ as
an instance method instead of a class method. In that case, +read_packed+ instance method is called with
the same arguments and should modify +self+ accordingly (instead of returning a new object).
It is not necessary to return +self+. The previous example can thus be shortened:
class MyHeader
#...
def read_packed(packedio, options)
self.signature, self.nb_blocks = packedio >> [String, {:bytes => 3}] >> :short
end
end
== Filter
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:
String.packers.set :length_encoded do |packer|
packer.write { |packedio| packedio << length << self }
packer.read { |packedio| packedio.read(packedio.read(Integer)) }
end
"hello!".pack(:length_encoded) ===> "\000\000\000\006hello!"
["this", "is", "great!"].pack(*[:length_encoded]*3).unpack(*[:length_encoded]*3) ===> ["this", "is", "great!"]
Note that the +write+ block will be executed as an instance method (which is why we could use +length+ & +self+),
while +read+ is a normal block that must return the newly read object.
== Inheritance
A final note to say that packers are inherited in some way. For instance one could define a filter for all objects:
Object.packers.set :with_class do |packer|
packer.write { |io| io << [self.class.name, :length_encoded] << self }
packer.read do |io|
klass = eval(io.read(:length_encoded))
io.read(klass)
end
end
[42, MyHeader.new("Wow", 1)].pack(:with_class, :with_class).unpack(:with_class, :with_class) ===> [42, MyHeader.new("Wow", 1)]
= License
packable is licensed under the terms of the MIT License, see the included LICENSE file.
Author:: Marc-André Lafortune packable-1.3.6/Rakefile 0000644 0000041 0000041 00000000270 12202633725 014761 0 ustar www-data www-data require "bundler/gem_tasks"
require 'rake/testtask'
Rake::TestTask.new(:test) do |test|
test.libs << 'lib' << 'test'
test.pattern = 'test/**/*_test.rb'
test.verbose = false
end
packable-1.3.6/LICENSE.txt 0000644 0000041 0000041 00000002064 12202633725 015142 0 ustar www-data www-data Copyright (c) 2012 Marc-Andre Lafortune
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. packable-1.3.6/checksums.yaml.gz 0000444 0000041 0000041 00000000416 12202633725 016604 0 ustar www-data www-data WRe;R0D"cIdR08=%tjwoF];qxy2ԺF:`".vK'lњٽhFs6(GU6R-RYPky#fBӱ14 @J6gy(OI#m
auV-|[
=("&7vgs,>͚o1^il?Vի+5Sr
packable-1.3.6/CHANGELOG.rdoc 0000644 0000041 0000041 00000001060 12202633725 015452 0 ustar www-data www-data = Packable --- History
== Version 1.3 - April 8, 2009
Added :endian => :native for Integers and Floats (thanks to Jay Daley)
Raises an exception if :endian is not one of :little, :big, :network or :native
Added some tests for Bignum
== Version 1.2 - April 2nd, 2009
Compatible with ruby 1.9.1.
The 'jungle_survival_kit' is now in its own 'backports' gem.
== Version 1.1 - December 17, 2008
Fixed bug when packing objects implementing to_ary
Added inheritance of shortcuts & filters to documentation
== Version 1.0 - December 17, 2008
=== Initial release.
packable-1.3.6/metadata.yml 0000644 0000041 0000041 00000003732 12202633725 015625 0 ustar www-data www-data --- !ruby/object:Gem::Specification
name: packable
version: !ruby/object:Gem::Version
version: 1.3.6
platform: ruby
authors:
- Marc-André Lafortune
autorequire:
bindir: bin
cert_chain: []
date: 2013-08-11 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
name: backports
requirement: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
description: If you need to do read and write binary data, there is of course ='
- !ruby/object:Gem::Version
version: '0'
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - '>='
- !ruby/object:Gem::Version
version: '0'
requirements: []
rubyforge_project:
rubygems_version: 2.0.3
signing_key:
specification_version: 4
summary: Extensive packing and unpacking capabilities
test_files:
- test/packing_doc_test.rb
- test/packing_test.rb
- test/test_helper.rb
has_rdoc:
packable-1.3.6/packable.gemspec 0000644 0000041 0000041 00000001626 12202633725 016431 0 ustar www-data www-data # -*- encoding: utf-8 -*-
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'packable/version'
Gem::Specification.new do |gem|
gem.name = "packable"
gem.version = Packable::VERSION
gem.authors = ["Marc-André Lafortune"]
gem.email = ["github@marc-andre.ca"]
gem.description = %q{If you need to do read and write binary data, there is of course