Initial commit.
authorTilman Sauerbeck <tilman@code-monkey.de>
Sat, 26 Mar 2005 01:45:38 +0000 (01:45 +0000)
committerTilman Sauerbeck <tilman@code-monkey.de>
Sat, 26 Mar 2005 01:45:38 +0000 (01:45 +0000)
16 files changed:
AUTHORS [new file with mode: 0644]
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
README [new file with mode: 0644]
Rakefile [new file with mode: 0644]
ext/ext.c [new file with mode: 0644]
lib/eet.rb [new file with mode: 0644]
test/common.rb [new file with mode: 0644]
test/test_basic.rb [new file with mode: 0644]
test/test_broken_classes.rb [new file with mode: 0644]
test/test_chunk.rb [new file with mode: 0644]
test/test_hash.rb [new file with mode: 0644]
test/test_list.rb [new file with mode: 0644]
test/test_misc.rb [new file with mode: 0644]
test/test_stream.rb [new file with mode: 0644]
test/test_sub.rb [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..4f7f7d1
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,5 @@
+--
+$Id: AUTHORS 1 2005-03-26 01:45:38Z tilman $
+++
+
+Tilman Sauerbeck (tilman at code-monkey de)
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..e800483
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,24 @@
+--
+$Id: COPYING 1 2005-03-26 01:45:38Z tilman $
+++
+
+Copyright (c) 2005 Tilman Sauerbeck (tilman at code-monkey de)
+
+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.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..e251520
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,123 @@
+--
+$Id: ChangeLog 1 2005-03-26 01:45:38Z tilman $
+++
+
+2005-03-26 Tilman Sauerbeck (tilman at code-monkey de)
+        * README, Rakefile: Require Rake 0.5.0 or greater
+        * Rakefile: Added a PackageTask
+
+2005-03-25 Tilman Sauerbeck (tilman at code-monkey de)
+        * test/test_broken_classes.rb: Code cleanup
+
+2005-03-23 Tilman Sauerbeck (tilman at code-monkey de)
+        * ext/ext.c, test/test_misc.rb: Eet::File methods check whether
+          EET entry keys contain binary zeroes
+
+2005-03-16 Tilman Sauerbeck (tilman at code-monkey de)
+        * lib/eet.rb: Fixed Eet::Stream#initialize
+
+2005-03-06 Tilman Sauerbeck (tilman at code-monkey de)
+        * Rakefile: fixed file permissions for eet.rb
+
+2005-03-05 Tilman Sauerbeck (tilman at code-monkey de)
+        * Rakefile: use FileUtils to install eet.rb and eet_ext.so
+
+2005-03-03 Tilman Sauerbeck (tilman at code-monkey de)
+        * ext/ext.c: EET_FILE_MODE_RW has been renamed to
+          EET_FILE_MODE_READ_WRITE
+
+2005-02-15 Tilman Sauerbeck (tilman at code-monkey de)
+        * lib/eet.rb, test/hash.rb: Removed the :hash format hack
+        * test/basic.rb: Renamed to test_basic.rb
+        * test/broken_classes.rb: Renamed to test_broken_classes.rb
+        * test/chunk.rb: Renamed to test_chunk.rb
+        * test/hash.rb: Renamed to test_hash.rb
+        * test/list.rb: Renamed to test_list.rb
+        * test/misc.rb: Renamed to test_misc.rb
+        * test/stream.rb: Renamed to test_stream.rb
+        * test/sub.rb: Renamed to test_sub.rb
+        * README: Removed documentation for the :hash format hack
+        * lib/eet.rb: Added :sub format specifier to Array#to_eet_chunks
+        * lib/eet.rb, README: Removed edd_name entry from property hash
+
+2005-02-14 Tilman Sauerbeck (tilman at code-monkey de)
+        * ext/ext.c: Enable highest compression for images
+        * lib/eet.rb: Added Eet::VERSION
+
+2005-02-13 Tilman Sauerbeck (tilman at code-monkey de)
+        * lib/eet.rb: If a hash is encoded with the :hash type
+          specifier, make sure that the keys/values are strings
+        * README: Documentation update
+        * Rakefile: Split out eet-config calls, to catch missing libeet
+          early
+
+2005-02-10 Tilman Sauerbeck (tilman at code-monkey de)
+        * lib/eet.rb, test/broken_classes.rb:
+          Implemented Object#to_eet_name and Object#to_eet_properties.
+          Adapted Object#to_eet's error checking and the broken classes
+          test.
+        * test/misc.rb: Use the new implementations of
+          #to_eet_name and #to_eet_properties
+        * test/misc.rb: Added a test for Object#to_eet_name and
+          Object#to_eet_properties
+        * Rakefile, test/hash.[rb,c], test/utils.c:
+          Reworked the hash test to not depend on hash.c/utils.c
+          anymore.
+        * lib/eet.rb: Added documentation for Object#to_eet_name and
+          Object#to_eet_properties
+
+2005-02-09 Tilman Sauerbeck (tilman at code-monkey de)
+        * test/sub.rb, test/sub.c: Reworked the sub test to not
+          depend on sub.c anymore
+        * test/list.rb, test/list.c: Reworked the list test to not
+          depend on list.c anymore
+
+2005-02-08 Tilman Sauerbeck (tilman at code-monkey de)
+        * lib/eet.rb: Eet::Chunk.new checks the tag for binary zeroes
+          again
+        * test/chunk.rb: Added a test for binary zeroes in tags
+        * lib/eet.rb: Added Eet::Chunk.deserialize
+        * test/chunk.rb: Added a test for Eet::Chunk.deserialize
+        * lib/eet.rb: Make sure Eet::Chunk.new is passed strings only
+        * lib/eet.rb: Added Eet::ChunkError
+        * test/chunk.rb: Added tests for the error handling in
+          Eet::Chunk.deserialize
+        * lib/eet.rb, test/stream.rb: Added Eet::Stream.deserialize
+        * test/basic.rb, test/basic.c: Rewrote basic test to not
+          depend on basic.c anymore
+
+2005-02-07 Tilman Sauerbeck (tilman at code-monkey de)
+        * test/list.rb, test/sub.rb: Make sure to use an array as
+          the hash values in Object#to_eet_properties
+        * lib/eet.rb: Perform more aggressive type checking in
+          Object#to_eet. Added Eet::EetError, Eet::PropertyError and
+          Eet::NameError.
+        * test/chunk.rb, test/stream.rb: Added tests for the Eet::Chunk
+          and Eet::Stream classes
+        * ext/ext.c, test/misc.rb: Raise IOError if Eet::File#list is
+          called in write-only mode
+        * test/broken_classes.rb: Added tests for the errors that
+          Object#to_eet may raise
+        * test/basic.[c,rb]: Added a test for 64bit integers
+        * lib/eet.rb: Pack 32bit integers with the "V" specifier instead
+          of "i", to enforce little-endian format
+
+2005-02-05 Tilman Sauerbeck (tilman at code-monkey de)
+        * ext/ext.c: Added Eet::File#read_image
+
+2005-02-04 Tilman Sauerbeck (tilman at code-monkey de)
+        * test/misc.rb: Removed nested assertions
+        * ext/ext.c: raise IOError on zero writes
+        * test/misc.rb: added test_zero_write
+
+2005-02-03 Tilman Sauerbeck (tilman at code-monkey de)
+        * test/misc.rb: Added some test for Eet::File#read and improved
+          other tests
+        * lib/eet.rb: Don't check for binary zeroes in tags
+
+2005-02-01 Tilman Sauerbeck (tilman at code-monkey de)
+        * lib/eet.rb: Reworked Stream/Chunk interfaces
+        * lib/eet.rb: Validate input data in Chunk.new
+
+2005-01-20 Tilman Sauerbeck (tilman at code-monkey de)
+        * Added some documentation
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..55ce271
--- /dev/null
+++ b/README
@@ -0,0 +1,93 @@
+--
+$Id: README 1 2005-03-26 01:45:38Z tilman $
+++
+
+= ruby-eet -- Ruby bindings for EET
+
+ruby-eet allows you to dump Ruby objects to disk in the EET file format.
+Compatibility with EET Data Descriptors, as used in the C API of libeet,
+is given.
+
+ruby-eet is maintained by:
+
+:include: AUTHORS
+
+== License
+
+ruby-eet is available under an MIT-style license.
+
+:include: COPYING
+
+== Download
+
+The latest version of ruby-eet can be found at
+http://code-monkey.de/projects/ruby-efl.html
+
+== Dependencies
+
+ruby-eet depends on Rake[http://rake.rubyforge.org] 0.5.0 or greater
+and EET[http://www.enlightenment.org].
+
+== Installation
+
+Run "rake install" to install ruby-eet.
+
+== Usage
+
+=== Basics
+
+Each entry in an EET file consists of an unique key and the data that's
+associated with that key.
+
+First, you have to open an EET file by calling Eet::File.open.
+
+Now, you can store arbitrary data in the EET file with Eet::File#write.
+To read the data from the EET file, use Eet::File#read.
+
+If you want to store/retrieve image data, see Eet::File#read_image and
+Eet::File#write_image.
+
+=== Serializing objects
+
+ruby-eet also supports the serialization of objects which uses the same
+format as the C API uses for its EET data descriptors.
+At the time of this writing, deserialization, i.e. read support, isn't
+implemented yet, though.
+
+Example:
+
+  class Foo
+    def initialize
+      @str = "bar"
+      @int = 1024
+    end
+  end
+
+Now, Foo.new.to_eet will serialize the object into EET format.
+All of the objects instance variables will be serialized, and the class
+name will be used as the tag.
+
+To override the tag, you just need to implement Foo#to_eet_name:
+
+  class Foo
+    def to_eet_name
+      "Blah"
+    end
+  end
+
+To control what information is stored for an object, override the method
+to_eet_properties.
+
+to_eet_properties returns a hash containing keys that are the names of
+the stored properties. Each hash value is an array that contains the
+value to store at least. Optionally, it can also contain a type specifier
+to enforce specific encoding.
+
+==== Type specifiers
+
+For fixnums and bignums, the valid type specifiers are :char, :short,
+:long_long which enforce encoding as 1 byte, 2 byte or 8 byte value
+respectively. The default is to encode the value as a 4 byte value.
+
+For floats, the valid type specifier is :double, which enforces encoding
+as an 8 byte value. The default is to encode the value as a 4 byte value.
diff --git a/Rakefile b/Rakefile
new file mode 100644 (file)
index 0000000..ab2676f
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,71 @@
+# $Id: Rakefile 1 2005-03-26 01:45:38Z tilman $
+
+require "rbconfig"
+require "rake/clean"
+require "rake/testtask"
+require "rake/rdoctask"
+require "rake/packagetask"
+
+# make sure we run at least Rake 0.5.0
+if (RAKEVERSION.split(".").map { |p| p.to_i } <=> [0, 5, 0]) < 0
+       fail("Rake 0.5.0 or greater required")
+end
+
+ext_obj = "ext/ext.o"
+ext_lib = "ext/eet_ext.so"
+
+CLOBBER.include(ext_obj)
+CLOBBER.include(ext_lib)
+CLOBBER.include(FileList["test/*.eet"])
+
+archdir = Config::CONFIG["archdir"]
+sitearchdir = Config::CONFIG["sitearchdir"]
+libdir = Config::CONFIG["libdir"]
+sitelibdir = Config::CONFIG["sitelibdir"]
+rubylib = Config::CONFIG["LIBRUBYARG_SHARED"]
+destdir = "#{ENV["DESTDIR"]}"
+eet_cflags = `eet-config --cflags`.strip
+eet_libs = `eet-config --libs`.strip
+cflags = "#{ENV["CFLAGS"]} #{eet_cflags}"
+ldflags = "#{ENV["LDFLAGS"]} #{eet_libs}"
+
+task :default => [ext_lib]
+
+rule ".o" => [".c"] do |t|
+       sh "cc #{cflags} -fPIC -I #{archdir} #{t.source} -c -o #{t.name}"
+end
+
+file ext_lib => ext_obj do |t|
+       sh "cc #{ldflags} -shared -Wl -L #{libdir} #{rubylib} " +
+          "#{t.prerequisites.join(" ")} -o #{t.name}"
+end
+
+task :install => [ext_lib] do |t|
+       d = destdir + sitearchdir
+
+       FileUtils::Verbose.install(ext_lib, d, :mode => 0755)
+       FileUtils::Verbose.install("lib/eet.rb", d, :mode => 0644)
+end
+
+task :test => [ext_lib]
+
+Rake::TestTask.new do |t|
+       t.libs << "ext"
+       t.test_files = FileList["test/test_*.rb"]
+       t.verbose = true
+end
+
+Rake::RDocTask.new do |t|
+       t.rdoc_dir = "doc"
+       t.title = "ruby-eet - EET bindings for Ruby"
+       t.options = ["--line-numbers", "--inline-source", "--main", "README"]
+       t.rdoc_files.include("README", "COPYING", "AUTHORS", "ChangeLog",
+                            "ext/ext.c", "lib/eet.rb")
+end
+
+Rake::PackageTask.new("ruby-eet") do |t|
+       md = `grep VERSION lib/eet.rb`.match(/VERSION = \"(.*)\"/)
+       t.version = md.captures.first
+       t.need_tar_gz = true
+       t.package_files.include("[A-Z]*", "ext/*.c", "lib/*.rb", "test/*.rb")
+end
diff --git a/ext/ext.c b/ext/ext.c
new file mode 100644 (file)
index 0000000..f7860d6
--- /dev/null
+++ b/ext/ext.c
@@ -0,0 +1,416 @@
+/*
+ * $Id: ext.c 1 2005-03-26 01:45:38Z tilman $
+ *
+ * Copyright (c) 2005 Tilman Sauerbeck (tilman at code-monkey de)
+ *
+ * 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.
+ */
+
+#include <Eet.h>
+#include <ruby.h>
+
+#define CHECK_KEY(key) \
+       Check_Type ((key), T_STRING); \
+       if (rb_funcall ((key), rb_intern ("include?"), \
+                           1, INT2FIX (0)) == Qtrue) \
+               rb_raise (rb_eArgError, "key must not contain binary zeroes");
+
+static VALUE c_close (VALUE self);
+
+static void
+c_free (Eet_File **ef)
+{
+       if (*ef) {
+               eet_close (*ef);
+               *ef = NULL;
+
+               eet_shutdown ();
+       }
+
+       free (ef);
+}
+
+static VALUE
+c_alloc (VALUE klass)
+{
+       Eet_File **ef = NULL;
+
+       return Data_Make_Struct (klass, Eet_File *, NULL, c_free, ef);
+}
+
+/*
+ * call-seq:
+ *  Eet::File.open(file [, mode] )                -> ef or nil
+ *  Eet::File.open(file [, mode] ) { |ef| block } -> nil
+ *
+ * If a block isn't specified, Eet::File.open is a synonym for
+ * Eet::File.new.
+ * If a block is given, it will be invoked with the
+ * Eet::File object as a parameter, and the file will be
+ * automatically closed when the block terminates. The call always
+ * returns +nil+ in this case.
+ */
+static VALUE
+c_open (int argc, VALUE *argv, VALUE klass)
+{
+       VALUE obj = rb_class_new_instance (argc, argv, klass);
+
+       if (rb_block_given_p ())
+               return rb_ensure (rb_yield, obj, c_close, obj);
+       else
+               return obj;
+}
+
+/*
+ * call-seq:
+ *  Eet::File.new(file [, mode] ) -> ef or nil
+ *
+ * Creates an Eet::File object for _file_.
+ *
+ * _file_ is opened with the specified mode (defaulting to "r").
+ * Possible values for _mode_ are "r" for read-only access,
+ * "w" for write-only access and "r+" for read/write access.
+ */
+static VALUE
+c_init (int argc, VALUE *argv, VALUE self)
+{
+       VALUE file = Qnil, mode = Qnil;
+       Eet_File **ef = NULL;
+       Eet_File_Mode m = EET_FILE_MODE_READ;
+       const char *tmp;
+
+       Data_Get_Struct (self, Eet_File *, ef);
+
+       rb_scan_args (argc, argv, "11", &file, &mode);
+
+       Check_Type (file, T_STRING);
+
+       if (!NIL_P (mode)) {
+               Check_Type (mode, T_STRING);
+
+               tmp = StringValuePtr (mode);
+               if (!strcmp (tmp, "r+"))
+                       m = EET_FILE_MODE_READ_WRITE;
+               else if (!strcmp (tmp, "w"))
+                       m = EET_FILE_MODE_WRITE;
+               else if (strcmp (tmp, "r"))
+                       rb_raise (rb_eArgError, "illegal access mode %s", tmp);
+       }
+
+       eet_init ();
+
+       *ef = eet_open (StringValuePtr (file), m);
+       if (!*ef) {
+               switch (m) {
+                       case EET_FILE_MODE_READ_WRITE:
+                       case EET_FILE_MODE_WRITE:
+                               tmp = "Permission denied - %s";
+                               break;
+                       default:
+                               tmp = "File not found - %s";
+                               break;
+               }
+
+               rb_raise (rb_eRuntimeError, tmp, file);
+       }
+
+       return self;
+}
+
+/*
+ * call-seq:
+ *  ef.close -> ef
+ *
+ * Closes _ef_ and flushes any pending writes.
+ * _ef_ is unavailable for any further data operations;
+ * an +IOError+ is raised if such an attempt is made.
+ *
+ * Eet::File objects are automatically closed when they
+ * are claimed by the garbage collector.
+ */
+static VALUE
+c_close (VALUE self)
+{
+       Eet_File **ef = NULL;
+
+       Data_Get_Struct (self, Eet_File *, ef);
+
+       if (!*ef)
+               rb_raise (rb_eIOError, "closed stream");
+       else {
+               eet_close (*ef);
+               *ef = NULL;
+
+               eet_shutdown ();
+       }
+
+       return self;
+}
+
+/*
+ * call-seq:
+ *  ef.list([glob]) -> array
+ *
+ * Returns an Array of entries in _ef_ that match the shell glob
+ * _glob_ (defaulting to "*").
+ */
+static VALUE
+c_list (int argc, VALUE *argv, VALUE self)
+{
+       VALUE glob = Qnil, ret;
+       Eet_File **ef = NULL;
+       char **entries, *tmp = "*";
+       int i, count = 0;
+
+       Data_Get_Struct (self, Eet_File *, ef);
+
+       if (!*ef)
+               rb_raise (rb_eIOError, "closed stream");
+
+       switch (eet_mode_get (*ef)) {
+               case EET_FILE_MODE_READ:
+               case EET_FILE_MODE_READ_WRITE:
+                       break;
+               default:
+                       rb_raise (rb_eIOError, "cannot list entries");
+       }
+
+       rb_scan_args (argc, argv, "01", &glob);
+
+       if (!NIL_P (glob)) {
+               Check_Type (glob, T_STRING);
+               tmp = StringValuePtr (glob);
+       }
+
+       ret = rb_ary_new ();
+
+       entries = eet_list (*ef, tmp, &count);
+
+       for (i = 0; i < count; i++)
+               rb_ary_push (ret, rb_str_new2 (entries[i]));
+
+       free (entries);
+
+       return ret;
+}
+
+/*
+ * call-seq:
+ *  ef.delete(key) -> ef
+ *
+ * Deletes the entry from _ef_ that is stored as _key_.
+ * If the entry cannot be deleted, an +IOError+ is raised,
+ * otherwise _ef_ is returned.
+ */
+static VALUE
+c_delete (VALUE self, VALUE key)
+{
+       Eet_File **ef = NULL;
+       char *tmp;
+
+       Data_Get_Struct (self, Eet_File *, ef);
+
+       if (!*ef)
+               rb_raise (rb_eIOError, "closed stream");
+
+       CHECK_KEY (key);
+
+       tmp = StringValuePtr (key);
+
+       if (!eet_delete (*ef, tmp))
+               rb_raise (rb_eIOError, "cannot delete entry - %s", tmp);
+
+       return self;
+}
+
+/*
+ * call-seq:
+ *  ef.read(key) -> string
+ *
+ * Reads an entry from _ef_ that is stored as _key_.
+ * If the data cannot be read, an +IOError+ is raised,
+ * otherwise a String is returned that contains the data.
+ */
+static VALUE
+c_read (VALUE self, VALUE key)
+{
+       VALUE ret;
+       Eet_File **ef = NULL;
+       void *data;
+       int size = 0;
+
+       Data_Get_Struct (self, Eet_File *, ef);
+
+       if (!*ef)
+               rb_raise (rb_eIOError, "closed stream");
+
+       CHECK_KEY (key);
+
+       data = eet_read (*ef, StringValuePtr (key), &size);
+       if (!data)
+               rb_raise (rb_eIOError, "cannot read entry - %s", key);
+
+       ret = rb_str_new (data, size);
+
+       free (data);
+
+       return ret;
+}
+
+/*
+ * call-seq:
+ *  ef.write(key, data [, compress] ) -> integer
+ *
+ * Stores _data_ in _ef_ as _key_.
+ * If _compress_ is true (which is the default), the data will be
+ * compressed.
+ * If the data cannot be written, an +IOError+ is raised,
+ * otherwise a the number of bytes written is returned.
+ */
+static VALUE
+c_write (int argc, VALUE *argv, VALUE self)
+{
+       VALUE key = Qnil, buf = Qnil, comp = Qnil;
+       Eet_File **ef = NULL;
+       int n;
+
+       Data_Get_Struct (self, Eet_File *, ef);
+
+       if (!*ef)
+               rb_raise (rb_eIOError, "closed stream");
+
+       rb_scan_args (argc, argv, "21", &key, &buf, &comp);
+
+       if (NIL_P (comp))
+               comp = Qtrue;
+
+       CHECK_KEY (key);
+       Check_Type (buf, T_STRING);
+
+       n = eet_write (*ef, StringValuePtr (key),
+                      StringValuePtr (buf), RSTRING (buf)->len,
+                      comp == Qtrue);
+       if (!n)
+               rb_raise (rb_eIOError, "couldn't write to file");
+       else
+               return INT2FIX (n);
+}
+
+/*
+ * call-seq:
+ *  ef.read_image(key) -> array
+ *
+ * Reads an image entry from _ef_ that is stored as _key_.
+ * If the data cannot be read, an +IOError+ is raised,
+ * otherwise an Array is returned that contains the image data,
+ * the image width, the image height and a boolean that indicates
+ * whether the image has an alpha channel or not.
+ */
+static VALUE
+c_read_image (VALUE self, VALUE key)
+{
+       VALUE ret;
+       Eet_File **ef = NULL;
+       void *data;
+       int w = 0, h = 0, has_alpha = 0;
+
+       Data_Get_Struct (self, Eet_File *, ef);
+
+       if (!*ef)
+               rb_raise (rb_eIOError, "closed stream");
+
+       CHECK_KEY (key);
+
+       data = eet_data_image_read (*ef, StringValuePtr (key), &w, &h,
+                                   &has_alpha, NULL, NULL, NULL);
+       if (!data)
+               rb_raise (rb_eIOError, "cannot read entry - %s", key);
+
+       ret = rb_ary_new3 (4, rb_str_new (data, w * h * 4),
+                              INT2FIX (w), INT2FIX (h),
+                              has_alpha ? Qtrue : Qfalse);
+       free (data);
+
+       return ret;
+}
+
+/*
+ * call-seq:
+ *  ef.write_image(key, image_data, w, h [, alpha] ) -> integer
+ *
+ * Stores _image_data_ with width _w_ and height _h_ in _ef_ as _key_.
+ * Pass true for _alpha_ if the image contains an alpha channel.
+ * If the data cannot be written, an +IOError+ is raised,
+ * otherwise a the number of bytes written is returned.
+ */
+static VALUE
+c_write_image (int argc, VALUE *argv, VALUE self)
+{
+       VALUE key = Qnil, buf = Qnil, w = Qnil, h = Qnil, has_alpha = Qnil;
+       Eet_File **ef = NULL;
+       int n;
+
+       Data_Get_Struct (self, Eet_File *, ef);
+
+       if (!*ef)
+               rb_raise (rb_eIOError, "closed stream");
+
+       rb_scan_args (argc, argv, "41", &key, &buf, &w, &h, &has_alpha);
+
+       if (NIL_P (has_alpha))
+               has_alpha = Qfalse;
+
+       CHECK_KEY (key);
+       Check_Type (buf, T_STRING);
+       Check_Type (w, T_FIXNUM);
+       Check_Type (h, T_FIXNUM);
+
+       if (!RSTRING (buf)->len)
+               return INT2FIX (0);
+
+       n = eet_data_image_write (*ef, StringValuePtr (key),
+                                 StringValuePtr (buf),
+                                 FIX2INT (w), FIX2INT (h),
+                                 has_alpha == Qtrue, 9, 0, 0);
+       if (!n)
+               rb_raise (rb_eIOError, "couldn't write to file");
+       else
+               return INT2FIX (n);
+}
+
+void
+Init_eet_ext ()
+{
+       VALUE m, c;
+
+       m = rb_define_module ("Eet");
+
+       c = rb_define_class_under (m, "File", rb_cObject);
+       rb_define_alloc_func (c, c_alloc);
+       rb_define_singleton_method (c, "open", c_open, -1);
+       rb_define_method (c, "initialize", c_init, -1);
+       rb_define_method (c, "close", c_close, 0);
+       rb_define_method (c, "list", c_list, -1);
+       rb_define_method (c, "delete", c_delete, 1);
+       rb_define_method (c, "read", c_read, 1);
+       rb_define_method (c, "write", c_write, -1);
+       rb_define_method (c, "read_image", c_read_image, 1);
+       rb_define_method (c, "write_image", c_write_image, -1);
+}
diff --git a/lib/eet.rb b/lib/eet.rb
new file mode 100644 (file)
index 0000000..abf8814
--- /dev/null
@@ -0,0 +1,233 @@
+#--
+# $Id: eet.rb 1 2005-03-26 01:45:38Z tilman $
+#
+# Copyright (c) 2005 Tilman Sauerbeck (tilman at code-monkey de)
+#
+# 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.
+
+require "eet_ext"
+
+class Object
+       # :call-seq:
+       #  object.to_eet -> string
+       #
+       # Serializes the receiver to EET format.
+       def to_eet
+               props = to_eet_properties
+
+               unless props.is_a?(Hash) && !props.empty?
+                       raise(Eet::PropertyError, "invalid EET properties")
+               end
+
+               eet_name = to_eet_name
+
+               if eet_name.to_str.length < 1 || eet_name.to_str.include?(0)
+                       raise(Eet::NameError, "invalid EET name")
+               end
+
+               stream = Eet::Stream.new
+
+               props.each_pair do |tag, arg|
+                       unless arg.is_a?(Array)
+                               raise(Eet::PropertyError, "hash value not an array")
+                       end
+
+                       value, type = arg
+                       next if value.nil?
+
+                       stream.push(*value.to_eet_chunks(tag, type))
+               end
+
+               chunk = Eet::Chunk.new(eet_name, stream.serialize)
+               Eet::Stream.new(chunk).serialize
+       end
+
+       def to_eet_chunks(tag, type = nil) # :nodoc:
+               [Eet::Chunk.new(tag, to_eet)]
+       end
+
+       protected
+
+       # :call-seq:
+       #  object.to_eet_name -> string
+       #
+       # Returns the tag that's stored with the data for _object_.
+       # If your class doesn't override this method, the class name will be
+       # used.
+       def to_eet_name
+               self.class.name
+       end
+
+       # :call-seq:
+       #  object.to_eet_properties -> hash
+       #
+       # Returns a hash that contains the properties that are stored for
+       # _object_.
+       # If your class doesn't override this method, all instance variables
+       # of _object_ will be stored.
+       def to_eet_properties
+               instance_variables.inject({}) do |h, var|
+                       h[var[1..-1]] = [instance_variable_get(var)]
+                       h
+               end
+       end
+end
+
+class Integer # :nodoc:
+       def to_eet_chunks(tag, type = nil)
+               fmt = case type
+               when :char: "c"
+               when :short: "v"
+               when :long_long: "q"
+               else "V"
+               end
+
+               data = [self].pack(fmt)
+               [Eet::Chunk.new(tag, data)]
+       end
+end
+
+class Float # :nodoc:
+       def to_eet_chunks(tag, type = nil)
+               fmt = case type
+               when :double: "%32.32f"
+               else "%16.16f"
+               end
+
+               data = fmt % self
+               [Eet::Chunk.new(tag, data + "\0")]
+       end
+end
+
+class String # :nodoc:
+       def to_eet_chunks(tag, type = nil)
+               [Eet::Chunk.new(tag, self + "\0")]
+       end
+end
+
+class TrueClass # :nodoc:
+       def to_eet_chunks(tag, type = nil)
+               [Eet::Chunk.new(tag, [1].pack("c"))]
+       end
+end
+
+class FalseClass # :nodoc:
+       def to_eet_chunks(tag, type = nil)
+               [Eet::Chunk.new(tag, [0].pack("c"))]
+       end
+end
+
+class Array # :nodoc:
+       def to_eet_chunks(tag, type = nil)
+               case type
+               when :sub
+                       [Eet::Chunk.new(tag, self.to_eet)]
+               else
+                       # lists always hold subtypes
+                       map { |item| Eet::Chunk.new(tag, item.to_eet) }
+               end
+       end
+end
+
+class Hash # :nodoc:
+       def to_eet_chunks(tag, type = nil)
+               # lists always hold subtypes
+               map { |(key, value)| Eet::Chunk.new(tag, value.to_eet) }
+       end
+end
+
+module Eet
+       VERSION = "0.0.1"
+
+       class EetError < StandardError; end
+       class NameError < EetError; end
+       class PropertyError < EetError; end
+       class ChunkError < EetError; end
+
+       class Stream < Array # :nodoc:
+               def initialize(chunk = nil)
+                       super(chunk.nil? ? 0 : 1, chunk)
+               end
+
+               def serialize
+                       inject("") { |a, c| a << c.serialize }
+               end
+
+               def Stream.deserialize(data)
+                       data = data.to_str.dup
+                       s = Stream.new
+
+                       while data.length > 0
+                               s << Chunk.deserialize(data)
+                       end
+
+                       s
+               end
+       end
+
+       class Chunk # :nodoc:
+               attr_reader :tag, :data
+
+               def initialize(tag, data)
+                       if tag.to_str.include?(0)
+                               raise(ArgumentError,
+                                     "tag must not contain binary zeroes")
+                       end
+
+                       @tag = tag.to_str.dup.freeze
+                       @data = data.to_str.dup.freeze
+
+                       @size = @tag.length + 1 + @data.length
+
+                       # libeet uses a signed 32bit integer to store the
+                       # chunk size, so make sure we don't overflow it
+                       if @size >= (1 << 31)
+                               raise(ArgumentError, "tag or data too long")
+                       end
+               end
+
+               def serialize
+                       buf = "CHnK"
+                       buf << [@size].pack("V")
+                       buf << @tag << "\0" << @data
+               end
+
+               def Chunk.deserialize(data)
+                       if data.length < 8 || data[0, 4] != "CHnK"
+                               raise(ChunkError, "invalid data")
+                       end
+
+                       size = data[4, 4].unpack("V").first
+                       if size >= (1 << 31) || size > data.length - 8
+                               raise(ChunkError, "invalid chunk size")
+                       end
+
+                       unless data[8, size].include?(0)
+                               raise(ChunkError, "invalid chunk data")
+                       end
+
+                       c = Chunk.new(*data[8, size].split("\0", 2))
+
+                       data.replace(data[8 + size..-1] || "")
+
+                       c
+               end
+       end
+end
diff --git a/test/common.rb b/test/common.rb
new file mode 100644 (file)
index 0000000..877ecf4
--- /dev/null
@@ -0,0 +1,32 @@
+# $Id: common.rb 1 2005-03-26 01:45:38Z tilman $
+
+class WrappedString
+       def initialize(blah)
+               @blah = blah
+       end
+
+       private
+       def to_eet_name
+               "String"
+       end
+
+       def to_eet_properties
+               {"buf" => [@blah]}
+       end
+end
+
+module Test
+       module Unit
+               module Assertions
+                       def assert_run_successful(cmd, msg = nil)
+                               `#{cmd}`
+                               st = $?.exitstatus
+
+                               message = build_message(msg, <<EOT, st)
+Program failed with code <?>.
+EOT
+                               assert_block(message) { st == 0 }
+                       end
+               end
+       end
+end
diff --git a/test/test_basic.rb b/test/test_basic.rb
new file mode 100644 (file)
index 0000000..73a30bc
--- /dev/null
@@ -0,0 +1,68 @@
+# $Id: test_basic.rb 1 2005-03-26 01:45:38Z tilman $
+
+require "eet"
+require "test/unit"
+require "common"
+
+class BasicTestData
+       def initialize
+               @name = "moo"
+               @short = 512
+               @int = 1024
+               @long_long = (2 << 63) - 1
+               @flag = true
+               @float = 1234.0
+               @double = 12341234.0
+       end
+
+       private
+       def to_eet_name
+               "BasicTest"
+       end
+
+       def to_eet_properties
+               {"name" => [@name],
+                "i16" => [@short, :short],
+                "i32" => [@int],
+                "i64" => [@long_long, :long_long],
+                "flag" => [@flag],
+                "f32" => [@float],
+                "f64" => [@double, :double]}
+       end
+end
+
+class BasicTest < Test::Unit::TestCase
+       def test_basic
+               data = BasicTestData.new.to_eet
+               assert_not_nil(data)
+
+               stream = nil
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(data)
+               end
+
+               assert_equal(1, stream.length)
+               assert_equal("BasicTest", stream.first.tag)
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(stream.first.data)
+               end
+
+               assert_equal(7, stream.length)
+
+               values = {"name" => "moo\0",
+                         "i16" => "\0\2", "i32" => "\0\4\0\0",
+                         "i64" => "\377" * 8, "flag" => "\1",
+                         "f32" => "1234." + ("0" * 16) + "\0",
+                         "f64" => "12341234." + ("0" * 32) + "\0"}
+               values.each do |k, v|
+                       found = stream.find { |c| c.tag == k }
+                       assert_not_nil(found, "chunk not found - #{k}")
+                       assert_equal(v, found.data)
+                       stream.delete(found)
+               end
+
+               assert_equal([], stream)
+       end
+end
diff --git a/test/test_broken_classes.rb b/test/test_broken_classes.rb
new file mode 100644 (file)
index 0000000..54db171
--- /dev/null
@@ -0,0 +1,86 @@
+# $Id: test_broken_classes.rb 1 2005-03-26 01:45:38Z tilman $
+
+class BrokenTestData1
+end
+
+class BrokenTestData2
+       def to_eet_properties
+       end
+end
+
+class BrokenTestData3
+       def to_eet_properties
+               ["blah"]
+       end
+end
+
+class BrokenTestData4
+       def to_eet_properties
+               {}
+       end
+end
+
+class BrokenTestData5
+       def to_eet_name
+               "blah"
+       end
+
+       def to_eet_properties
+               {"" => nil}
+       end
+end
+
+class BrokenTestData6
+       def to_eet_name
+               "blah"
+       end
+
+       def to_eet_properties
+               {"blah" => nil}
+       end
+end
+
+class BrokenTestData7
+       def to_eet_properties
+               {"blah" => [nil]}
+       end
+end
+
+class BrokenTestData8
+       def to_eet_name
+       end
+
+       def to_eet_properties
+               {"blah" => [nil]}
+       end
+end
+
+class BrokenTestData9
+       def to_eet_name
+               ""
+       end
+
+       def to_eet_properties
+               {"blah" => [nil]}
+       end
+end
+
+class BrokenTest < Test::Unit::TestCase
+       def test_broken_properties
+               (1..6).each do |i|
+                       assert_raise(Eet::PropertyError) do
+                               Object.const_get("BrokenTestData#{i}").new.to_eet
+                       end
+               end
+       end
+
+       def test_broken_eet_names
+               assert_raise(NoMethodError) do
+                       BrokenTestData8.new.to_eet
+               end
+
+               assert_raise(Eet::NameError) do
+                       BrokenTestData9.new.to_eet
+               end
+       end
+end
diff --git a/test/test_chunk.rb b/test/test_chunk.rb
new file mode 100644 (file)
index 0000000..2891dba
--- /dev/null
@@ -0,0 +1,49 @@
+# $Id: test_chunk.rb 1 2005-03-26 01:45:38Z tilman $
+
+require "eet"
+require "test/unit"
+
+class ChunkTest < Test::Unit::TestCase
+       def setup
+               @data = Eet::Chunk.new("tag", "foo\0bar\0baz").serialize
+       end
+
+       def test_serialize
+               assert_equal("CHnK\017\0\0\0tag\0foo\0bar\0baz", @data)
+       end
+
+       def test_deserialize
+               chunk = nil
+
+               assert_nothing_raised do
+                       chunk = Eet::Chunk.deserialize(@data)
+               end
+
+               assert_equal("tag", chunk.tag)
+               assert_equal("foo\0bar\0baz", chunk.data)
+       end
+
+       def test_deserialize_invalid
+               assert_raise(Eet::ChunkError) do
+                       Eet::Chunk.deserialize("foobar" << "\377" * 4 << "tagdata")
+               end
+
+               assert_raise(Eet::ChunkError) do
+                       Eet::Chunk.deserialize("CHnK" << "\377" * 4 << "tagdata")
+               end
+
+               assert_raise(Eet::ChunkError) do
+                       Eet::Chunk.deserialize("CHnK\010\0\0\0tag0data")
+               end
+
+               assert_nothing_raised do
+                       Eet::Chunk.deserialize("CHnK\10\0\0\0tag\0data")
+               end
+       end
+
+       def test_catch_invalid_tag
+               assert_raise(ArgumentError) do
+                       Eet::Chunk.new("foo\0bar", "data")
+               end
+       end
+end
diff --git a/test/test_hash.rb b/test/test_hash.rb
new file mode 100644 (file)
index 0000000..b77dac3
--- /dev/null
@@ -0,0 +1,86 @@
+# $Id: test_hash.rb 1 2005-03-26 01:45:38Z tilman $
+
+require "eet"
+require "test/unit"
+require "common"
+
+class HashEntry
+       def initialize(key, value)
+               @key = key.to_str.dup.freeze
+               @value = value.to_str.dup.freeze
+       end
+end
+
+class HashTestData
+       def initialize
+               @hash = {"artist" => HashEntry.new("artist", "Amon Amarth"),
+                        "title" => HashEntry.new("title", "Death In Fire")}
+       end
+
+       private
+       def to_eet_name
+               "HashTest"
+       end
+
+       def to_eet_properties
+               {"hash" => [@hash]}
+       end
+end
+
+class HashTest < Test::Unit::TestCase
+       def test_hash
+               data = HashTestData.new.to_eet
+               assert_not_nil(data)
+
+               stream = nil
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(data)
+               end
+
+               assert_equal(1, stream.length)
+               assert_equal("HashTest", stream.first.tag)
+
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(stream.first.data)
+               end
+
+               assert_equal(2, stream.length)
+
+               2.times do
+                       hashlist = stream.find_all { |c| c.tag == "hash" }
+                       assert_equal(2, hashlist.length)
+
+                       hash = {"artist" => "Amon Amarth",
+                               "title" => "Death In Fire"}
+                       hash.each do |key, value|
+                               str_stream = nil
+
+                               assert_nothing_raised do
+                                       d = hashlist.shift.data
+                                       str_stream = Eet::Stream.deserialize(d)
+                               end
+
+                               assert_equal(1, str_stream.length)
+                               assert_equal("HashEntry", str_stream.first.tag)
+
+                               foo = nil
+
+                               assert_nothing_raised do
+                                       foo = Eet::Stream.deserialize(str_stream.first.data)
+                               end
+
+                               assert_equal(2, foo.length)
+
+                               {"key" => key, "value" => value}.each do |tag, data|
+                                       found = foo.find { |i| i.tag == tag }
+                                       assert_not_nil(found)
+                                       assert_equal(data + "\0", found.data)
+                               end
+                       end
+
+                       assert_equal([], hashlist)
+               end
+       end
+end
diff --git a/test/test_list.rb b/test/test_list.rb
new file mode 100644 (file)
index 0000000..782ba97
--- /dev/null
@@ -0,0 +1,82 @@
+# $Id: test_list.rb 1 2005-03-26 01:45:38Z tilman $
+
+require "eet"
+require "test/unit"
+require "common"
+
+class ListTestData
+       def initialize
+               @strings = {}
+               @strings2 = []
+
+               5.times do |i|
+                       @strings[i] = WrappedString.new("rubyrocks%i" % i)
+               end
+
+               #(6..10).each do |i|
+               5.times do |i|
+                       @strings2 << WrappedString.new("rubyrocks%i" % i)
+               end
+       end
+
+       private
+       def to_eet_name
+               "ListTest"
+       end
+
+       def to_eet_properties
+               {"stringlist_a" => [@strings],
+                "stringlist_h" => [@strings2]}
+       end
+end
+
+class ListTest < Test::Unit::TestCase
+       def test_list
+               data = ListTestData.new.to_eet
+               assert_not_nil(data)
+
+               stream = nil
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(data)
+               end
+
+               assert_equal(1, stream.length)
+               assert_equal("ListTest", stream.first.tag)
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(stream.first.data)
+               end
+
+               assert_equal(10, stream.length)
+
+               ["stringlist_a", "stringlist_h"].each do |tag|
+                       stringlist = stream.find_all { |c| c.tag == tag }
+                       assert_equal(5, stringlist.length)
+
+                       (0..4).each do |i|
+                               str_stream = nil
+
+                               assert_nothing_raised do
+                                       d = stringlist.shift.data
+                                       str_stream = Eet::Stream.deserialize(d)
+                               end
+
+                               assert_equal(1, str_stream.length)
+                               assert_equal("String", str_stream.first.tag)
+
+                               foo = nil
+
+                               assert_nothing_raised do
+                                       foo = Eet::Stream.deserialize(str_stream.first.data)
+                               end
+
+                               assert_equal(1, foo.length)
+                               assert_equal("buf", foo.first.tag)
+                               assert_equal("rubyrocks%i\0" % i, foo.first.data)
+                       end
+
+                       assert_equal([], stringlist)
+               end
+       end
+end
diff --git a/test/test_misc.rb b/test/test_misc.rb
new file mode 100644 (file)
index 0000000..e97634a
--- /dev/null
@@ -0,0 +1,177 @@
+# $Id: test_misc.rb 1 2005-03-26 01:45:38Z tilman $
+
+require "eet"
+require "test/unit"
+require "ftools"
+
+class Foo
+       def initialize
+               @foo = "foo"
+               @bar = true
+               @baz = 512
+       end
+end
+
+class MiscTest < Test::Unit::TestCase
+       def setup
+               @keys = ["key_foo", "key_bar", "key_baz"].sort
+               @dest = __FILE__.sub(/\.rb$/, ".eet")
+               @foo_data = Foo.new.to_eet
+
+               assert_not_nil(@foo_data)
+
+               File.rm_f(@dest)
+
+               Eet::File.open(@dest, "w") do |ef|
+                       @keys.each do |k|
+                               assert(ef.write(k, @foo_data) > 0)
+                       end
+               end
+       end
+
+       def test_stream_chunks
+               stream = nil
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(@foo_data)
+               end
+
+               assert_equal(1, stream.length)
+               assert_equal("Foo", stream.first.tag)
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(stream.first.data)
+               end
+
+               assert_equal(3, stream.length)
+               ["foo", "bar", "baz"].each do |tag|
+                       found = stream.find { |c| c.tag == tag }
+                       assert_not_nil(found)
+               end
+       end
+
+       def test_create_new
+               assert_raise(RuntimeError) do
+                       Eet::File.open("/foo/bar/baz/xyzzy", "w") { |ef| }
+               end
+       end
+
+       def test_catch_invalid_open_mode
+               assert_raise(ArgumentError) do
+                       Eet::File.open(@dest, @keys[0]) { |ef| }
+               end
+       end
+
+       def test_zero_write
+               assert_raise(IOError) do
+                       Eet::File.open(@dest, "w") do |ef|
+                               ef.write("empty", "")
+                       end
+               end
+       end
+
+       def test_open_existing_ro
+               # if mode isn't specified, the file must be opened read-only.
+               # assert that we cannot write in read-only mode.
+               Eet::File.open(@dest) do |ef|
+                       assert_raise(IOError) do
+                               ef.write("xyzzy", "data")
+                       end
+               end
+       end
+
+       def test_open_existing_w
+               # if the file is opened in write-only or read-write mode,
+               # we must be able to write to it.
+               ["w", "r+"].each do |mode|
+                       Eet::File.open(@dest, mode) do |ef|
+                               assert_equal(4, ef.write("xyzzy", "data"))
+                       end
+               end
+       end
+
+       def test_accessing_closed_file
+               ef = Eet::File.open(@dest, "r")
+               ef.close
+
+               assert_raise(IOError) do
+                       ef.read(@keys[0])
+               end
+       end
+
+       def test_delete_entry_ro
+               assert_raise(IOError) do
+                       Eet::File.open(@dest, "r") do |ef|
+                               ef.delete(@keys[0])
+                       end
+               end
+       end
+
+       def test_delete_nonexisting_entry
+               assert_raise(IOError) do
+                       Eet::File.open(@dest, "w") do |ef|
+                               ef.delete("nonexistingkey")
+                       end
+               end
+       end
+
+       def test_read
+               Eet::File.open(@dest, "r") do |ef|
+                       @keys.each do |k|
+                               assert_equal(@foo_data, ef.read(k))
+                       end
+               end
+
+               assert_raise(IOError) do
+                       Eet::File.open(@dest, "w") do |ef|
+                               ef.read(@keys[0])
+                       end
+               end
+       end
+
+       def test_list
+               ["r", "r+"].each do |mode|
+                       Eet::File.open(@dest, mode) do |ef|
+                               assert_equal(@keys, ef.list.sort)
+                               assert_equal(@keys, ef.list("*").sort, @keys)
+                               assert_equal([@keys[0]], ef.list(@keys[0]))
+                       end
+               end
+
+               assert_raise(IOError) do
+                       Eet::File.open(@dest, "w") do |ef|
+                               ef.list
+                       end
+               end
+       end
+
+       def test_delete
+               assert_nothing_raised do
+                       Eet::File.open(@dest, "r+") do |ef|
+                               ef.delete(@keys[0])
+                       end
+               end
+
+               Eet::File.open(@dest) do |ef|
+                       tmp = @keys.shift
+                       assert_equal(@keys, ef.list.sort)
+                       @keys.unshift(tmp)
+               end
+       end
+
+       def test_invalid_key
+               Eet::File.open(@dest, "r+") do |ef|
+                       assert_raise(ArgumentError) do
+                               ef.read("key_\0foo")
+                       end
+
+                       assert_raise(ArgumentError) do
+                               ef.write("key_\0foo", "data")
+                       end
+
+                       assert_raise(ArgumentError) do
+                               ef.delete("key_\0foo")
+                       end
+               end
+       end
+end
diff --git a/test/test_stream.rb b/test/test_stream.rb
new file mode 100644 (file)
index 0000000..68e7ea4
--- /dev/null
@@ -0,0 +1,42 @@
+# $Id: test_stream.rb 1 2005-03-26 01:45:38Z tilman $
+
+require "eet"
+require "test/unit"
+
+class StreamTest < Test::Unit::TestCase
+       def setup
+               stream = Eet::Stream.new
+               stream << Eet::Chunk.new("tag", "foo")
+               stream << Eet::Chunk.new("tag2", "bar")
+               stream << Eet::Chunk.new("tag23", "baz")
+
+               @data = stream.serialize
+               assert_not_nil(@data)
+       end
+
+       def test_serialize
+               assert_equal("CHnK\007\0\0\0tag\0foo" +
+                            "CHnK\010\0\0\0tag2\0bar" +
+                            "CHnK\011\0\0\0tag23\0baz", @data)
+       end
+
+       def test_deserialize
+               stream = nil
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(@data)
+               end
+
+               assert_equal(3, stream.length)
+
+               values = {"tag" => "foo", "tag2" => "bar", "tag23" => "baz"}
+               values.each do |k, v|
+                       found = stream.find { |c| c.tag == k }
+                       assert_not_nil(found, "chunk not found - #{k}")
+                       assert_equal(found.data, v)
+                       stream.delete(found)
+               end
+
+               assert_equal([], stream)
+       end
+end
diff --git a/test/test_sub.rb b/test/test_sub.rb
new file mode 100644 (file)
index 0000000..a92e5b4
--- /dev/null
@@ -0,0 +1,59 @@
+# $Id: test_sub.rb 1 2005-03-26 01:45:38Z tilman $
+
+require "eet"
+require "test/unit"
+require "common"
+
+class SubTestData
+       def initialize
+               @string = WrappedString.new("foobar")
+       end
+
+       private
+       def to_eet_name
+               "SubTest"
+       end
+
+       def to_eet_properties
+               {"sub_test" => [@string],
+                "nil_test" => [nil]}
+       end
+end
+
+class SubTest < Test::Unit::TestCase
+       def test_sub
+               data = SubTestData.new.to_eet
+               assert_not_nil(data)
+
+               stream = nil
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(data)
+               end
+
+               assert_equal(1, stream.length)
+               assert_equal("SubTest", stream.first.tag)
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(stream.first.data)
+               end
+
+               assert_equal(1, stream.length)
+               assert_equal("sub_test", stream.first.tag)
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(stream.first.data)
+               end
+
+               assert_equal(1, stream.length)
+               assert_equal("String", stream.first.tag)
+
+               assert_nothing_raised do
+                       stream = Eet::Stream.deserialize(stream.first.data)
+               end
+
+               assert_equal(1, stream.length)
+               assert_equal("buf", stream.first.tag)
+               assert_equal("foobar\0", stream.first.data)
+       end
+end