Relaxed rules for Ogg::Vorbis::Comments#merge!.
[ruby-vorbistagger.git] / ext / comments.c
index 23967289b5e20ad56222171dafb9279147b84ee7..03a237b104f87d0fbfd77f78e165dae2a828fb34 100644 (file)
  */
 
 #include <ruby.h>
+#include <st.h>
 #include <stdbool.h>
 #include <ctype.h>
 #include <assert.h>
 
 #include "vcedit.h"
 
-typedef struct {
-       vcedit_state *state;
-
-       VALUE items;
-} RbVorbisComments;
-
-static ID id_casecmp, id_replace, id_compare;
+static ID id_casecmp, id_replace, id_each, id_compare;
 
 void
 comments_init (VALUE self, vcedit_state *state)
 {
-       RbVorbisComments *o;
+       VALUE items;
        vorbis_comment *vc;
        int i;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       o->state = state;
-       vcedit_state_ref (state);
+       vc = vcedit_comments (state);
 
-       vc = vcedit_comments (o->state);
-
-       o->items = rb_ary_new2 (vc->comments);
+       items = rb_iv_get (self, "items");
+       rb_ary_clear (items);
 
        for (i = 0; i < vc->comments; i++) {
-               VALUE k, v;
+               VALUE k, v, pair;
                char *ptr, *content = vc->user_comments[i];
 
                ptr = strchr (content, '=');
@@ -59,29 +50,26 @@ comments_init (VALUE self, vcedit_state *state)
 
                v = rb_str_new2 (ptr + 1);
 
-               rb_ary_store (o->items, i,
-                             rb_ary_new3 (2, k, v));
-       }
+               pair = rb_ary_new3 (2, k, v);
+               OBJ_FREEZE (pair);
 
-       rb_iv_set (self, "@items", o->items);
+               rb_ary_store (items, i, pair);
+       }
 }
 
 void
-comments_sync (VALUE self)
+comments_sync (VALUE self, vcedit_state *state)
 {
-       RbVorbisComments *o;
        vorbis_comment *vc;
        struct RArray *items;
        int i;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       vc = vcedit_comments (o->state);
+       vc = vcedit_comments (state);
 
        vorbis_comment_clear (vc);
        vorbis_comment_init (vc);
 
-       items = RARRAY (o->items);
+       items = RARRAY (rb_iv_get (self, "items"));
 
        for (i = 0; i < items->len; i++) {
                struct RArray *pair = RARRAY (items->ptr[i]);
@@ -91,27 +79,18 @@ comments_sync (VALUE self)
                                        StringValuePtr (pair->ptr[1]));
        }
 }
-
-static void
-c_mark (RbVorbisComments *o)
-{
-       rb_gc_mark (o->items);
-}
-
-static void
-c_free (RbVorbisComments *o)
-{
-       vcedit_state_unref (o->state);
-
-       ruby_xfree (o);
-}
-
+/*
+ * call-seq:
+ *  Ogg::Vorbis::Comments.new -> object
+ *
+ * Creates an Ogg::Vorbis::Comments object.
+ */
 static VALUE
-c_alloc (VALUE klass)
+c_init (VALUE self)
 {
-       RbVorbisComments *o;
+       rb_iv_set (self, "items", rb_ary_new ());
 
-       return Data_Make_Struct (klass, RbVorbisComments, c_mark, c_free, o);
+       return self;
 }
 
 /*
@@ -124,13 +103,10 @@ static VALUE
 c_inspect (VALUE self)
 {
        VALUE ret;
-       RbVorbisComments *o;
        struct RArray *items;
        int i;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       items = RARRAY (o->items);
+       items = RARRAY (rb_iv_get (self, "items"));
 
        ret = rb_str_buf_new (128);
        rb_str_buf_cat (ret, "{", 1);
@@ -160,11 +136,7 @@ c_inspect (VALUE self)
 static VALUE
 c_clear (VALUE self)
 {
-       RbVorbisComments *o;
-
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       rb_ary_clear (o->items);
+       rb_ary_clear (rb_iv_get (self, "items"));
 
        return self;
 }
@@ -180,13 +152,10 @@ static VALUE
 c_delete (VALUE self, VALUE key)
 {
        VALUE ret = Qnil;
-       RbVorbisComments *o;
        struct RArray *items;
        int i, pos = -1;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       items = RARRAY (o->items);
+       items = RARRAY (rb_iv_get (self, "items"));
 
        for (i = 0; i < items->len; i++) {
                struct RArray *pair = RARRAY (items->ptr[i]);
@@ -201,7 +170,7 @@ c_delete (VALUE self, VALUE key)
        }
 
        if (pos != -1)
-               rb_ary_delete_at (o->items, pos);
+               rb_ary_delete_at (rb_iv_get (self, "items"), pos);
 
        return ret;
 }
@@ -216,13 +185,10 @@ static VALUE
 c_keys (VALUE self)
 {
        VALUE ret;
-       RbVorbisComments *o;
        struct RArray *items;
        int i;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       items = RARRAY (o->items);
+       items = RARRAY (rb_iv_get (self, "items"));
        ret = rb_ary_new2 (items->len);
 
        for (i = 0; i < items->len; i++) {
@@ -244,13 +210,10 @@ static VALUE
 c_values (VALUE self)
 {
        VALUE ret;
-       RbVorbisComments *o;
        struct RArray *items;
        int i;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       items = RARRAY (o->items);
+       items = RARRAY (rb_iv_get (self, "items"));
        ret = rb_ary_new2 (items->len);
 
        for (i = 0; i < items->len; i++) {
@@ -271,11 +234,11 @@ c_values (VALUE self)
 static VALUE
 c_length (VALUE self)
 {
-       RbVorbisComments *o;
+       struct RArray *items;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
+       items = RARRAY (rb_iv_get (self, "items"));
 
-       return LONG2NUM (RARRAY (o->items)->len);
+       return LONG2NUM (items->len);
 }
 
 /*
@@ -287,11 +250,11 @@ c_length (VALUE self)
 static VALUE
 c_get_empty (VALUE self)
 {
-       RbVorbisComments *o;
+       struct RArray *items;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
+       items = RARRAY (rb_iv_get (self, "items"));
 
-       return !RARRAY(o->items)->len;
+       return items->len ? Qfalse : Qtrue;
 }
 
 /*
@@ -304,13 +267,10 @@ c_get_empty (VALUE self)
 static VALUE
 c_aref (VALUE self, VALUE key)
 {
-       RbVorbisComments *o;
        struct RArray *items;
        int i;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       items = RARRAY (o->items);
+       items = RARRAY (rb_iv_get (self, "items"));
 
        for (i = 0; i < items->len; i++) {
                struct RArray *pair = RARRAY (items->ptr[i]);
@@ -333,13 +293,11 @@ c_aref (VALUE self, VALUE key)
 static VALUE
 c_aset (VALUE self, VALUE key, VALUE value)
 {
-       RbVorbisComments *o;
+       VALUE tmp;
        struct RArray *items;
        int i;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       items = RARRAY (o->items);
+       items = RARRAY (rb_iv_get (self, "items"));
 
        for (i = 0; i < items->len; i++) {
                struct RArray *pair = RARRAY (items->ptr[i]);
@@ -352,7 +310,10 @@ c_aset (VALUE self, VALUE key, VALUE value)
                }
        }
 
-       rb_ary_push (o->items, rb_ary_new3 (2, key, value));
+       tmp = rb_ary_new3 (2, rb_str_dup_frozen (key), value);
+       OBJ_FREEZE (tmp);
+
+       rb_ary_push (rb_iv_get (self, "items"), tmp);
 
        return value;
 }
@@ -367,13 +328,10 @@ c_aset (VALUE self, VALUE key, VALUE value)
 static VALUE
 c_has_key (VALUE self, VALUE key)
 {
-       RbVorbisComments *o;
        struct RArray *items;
        int i;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       items = RARRAY (o->items);
+       items = RARRAY (rb_iv_get (self, "items"));
 
        for (i = 0; i < items->len; i++) {
                struct RArray *pair = RARRAY (items->ptr[i]);
@@ -387,6 +345,52 @@ c_has_key (VALUE self, VALUE key)
        return Qfalse;
 }
 
+static VALUE
+merge_cb (VALUE ar, VALUE self)
+{
+       struct RArray *pair = RARRAY (ar);
+
+       c_aset (self, pair->ptr[0], pair->ptr[1]);
+
+       return Qnil;
+}
+
+/*
+ * call-seq:
+ *  object.merge!(arg) -> object
+ *
+ * Adds the key-value pairs from *arg* to *object*, overwriting existing
+ * values if a key already existed in *object*.
+ *
+ * Note that *arg*'s each method needs to yield key-value pairs for this
+ * to work. This means that e.g. hashes and Ogg::Vorbis::Comments objects
+ * are supported as arguments.
+ */
+static VALUE
+c_merge (VALUE self, VALUE arg)
+{
+       if (!rb_respond_to (arg, id_each))
+               rb_raise (rb_eArgError, "invalid argument");
+
+       rb_iterate (rb_each, arg, merge_cb, self);
+
+       return self;
+}
+
+/*
+ * call-seq:
+ *  object.shift(hash) -> array or nil
+ *
+ * Removes the first key-value pair from *object* and returns it
+ * as the two-item array [key, value].
+ * If *object* is empty, +nil+ is returned.
+ */
+static VALUE
+c_shift (VALUE self)
+{
+       return rb_ary_shift (rb_iv_get (self, "items"));
+}
+
 /*
  * call-seq:
  *  object <=> other -> -1, 0 or 1
@@ -397,18 +401,14 @@ c_has_key (VALUE self, VALUE key)
 static VALUE
 c_compare (VALUE self, VALUE other)
 {
-       RbVorbisComments *o, *o2;
        struct RArray *a, *b;
        int i, j;
 
        if (rb_obj_is_kind_of (other, CLASS_OF (self)) != Qtrue)
                rb_raise (rb_eArgError, "invalid argument");
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-       Data_Get_Struct (other, RbVorbisComments, o2);
-
-       a = RARRAY (o->items);
-       b = RARRAY (o2->items);
+       a = RARRAY (rb_iv_get (self, "items"));
+       b = RARRAY (rb_iv_get (other, "items"));
 
        if (a->len < b->len)
                return -1;
@@ -443,13 +443,10 @@ c_compare (VALUE self, VALUE other)
 static VALUE
 c_each (VALUE self)
 {
-       RbVorbisComments *o;
        struct RArray *items;
        int i;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       items = RARRAY (o->items);
+       items = RARRAY (rb_iv_get (self, "items"));
 
        for (i = 0; i < items->len; i++) {
                struct RArray *pair = RARRAY (items->ptr[i]);
@@ -471,13 +468,10 @@ c_each (VALUE self)
 static VALUE
 c_each_key (VALUE self)
 {
-       RbVorbisComments *o;
        struct RArray *items;
        int i;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       items = RARRAY (o->items);
+       items = RARRAY (rb_iv_get (self, "items"));
 
        for (i = 0; i < items->len; i++) {
                struct RArray *pair = RARRAY (items->ptr[i]);
@@ -498,13 +492,10 @@ c_each_key (VALUE self)
 static VALUE
 c_each_value (VALUE self)
 {
-       RbVorbisComments *o;
        struct RArray *items;
        int i;
 
-       Data_Get_Struct (self, RbVorbisComments, o);
-
-       items = RARRAY (o->items);
+       items = RARRAY (rb_iv_get (self, "items"));
 
        for (i = 0; i < items->len; i++) {
                struct RArray *pair = RARRAY (items->ptr[i]);
@@ -522,8 +513,7 @@ Init_Comments (VALUE mVorbis)
 
        c = rb_define_class_under (mVorbis, "Comments", rb_cObject);
 
-       rb_define_alloc_func (c, c_alloc);
-
+       rb_define_method (c, "initialize", c_init, 0);
        rb_define_method (c, "inspect", c_inspect, 0);
        rb_define_method (c, "clear", c_clear, 0);
        rb_define_method (c, "delete", c_delete, 1);
@@ -534,6 +524,8 @@ Init_Comments (VALUE mVorbis)
        rb_define_method (c, "empty?", c_get_empty, 0);
        rb_define_method (c, "keys", c_keys, 0);
        rb_define_method (c, "values", c_values, 0);
+       rb_define_method (c, "merge!", c_merge, 1);
+       rb_define_method (c, "shift", c_shift, 0);
 
        rb_include_module (c, rb_mComparable);
        rb_define_method (c, "<=>", c_compare, 1);
@@ -545,9 +537,13 @@ Init_Comments (VALUE mVorbis)
 
        rb_define_alias (c, "size", "length");
        rb_define_alias (c, "each_pair", "each");
+       rb_define_alias (c, "key?", "has_key?");
+       rb_define_alias (c, "include?", "has_key?");
+       rb_define_alias (c, "member?", "has_key?");
 
        id_casecmp = rb_intern ("casecmp");
        id_replace = rb_intern ("replace");
+       id_each = rb_intern ("each");
        id_compare = rb_intern ("<=>");
 
        return c;