Relaxed rules for Ogg::Vorbis::Comments#merge!.
[ruby-vorbistagger.git] / ext / comments.c
1 /*
2  * Copyright (C) 2006 Tilman Sauerbeck (tilman at code-monkey de)
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation, version 2.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with this library; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16  * MA 02110-1301 USA
17  */
18
19 #include <ruby.h>
20 #include <st.h>
21 #include <stdbool.h>
22 #include <ctype.h>
23 #include <assert.h>
24
25 #include "vcedit.h"
26
27 static ID id_casecmp, id_replace, id_each, id_compare;
28
29 void
30 comments_init (VALUE self, vcedit_state *state)
31 {
32         VALUE items;
33         vorbis_comment *vc;
34         int i;
35
36         vc = vcedit_comments (state);
37
38         items = rb_iv_get (self, "items");
39         rb_ary_clear (items);
40
41         for (i = 0; i < vc->comments; i++) {
42                 VALUE k, v, pair;
43                 char *ptr, *content = vc->user_comments[i];
44
45                 ptr = strchr (content, '=');
46                 assert (ptr);
47
48                 k = rb_str_new (content, ptr - content);
49                 OBJ_FREEZE (k);
50
51                 v = rb_str_new2 (ptr + 1);
52
53                 pair = rb_ary_new3 (2, k, v);
54                 OBJ_FREEZE (pair);
55
56                 rb_ary_store (items, i, pair);
57         }
58 }
59
60 void
61 comments_sync (VALUE self, vcedit_state *state)
62 {
63         vorbis_comment *vc;
64         struct RArray *items;
65         int i;
66
67         vc = vcedit_comments (state);
68
69         vorbis_comment_clear (vc);
70         vorbis_comment_init (vc);
71
72         items = RARRAY (rb_iv_get (self, "items"));
73
74         for (i = 0; i < items->len; i++) {
75                 struct RArray *pair = RARRAY (items->ptr[i]);
76
77                 vorbis_comment_add_tag (vc,
78                                         StringValuePtr (pair->ptr[0]),
79                                         StringValuePtr (pair->ptr[1]));
80         }
81 }
82 /*
83  * call-seq:
84  *  Ogg::Vorbis::Comments.new -> object
85  *
86  * Creates an Ogg::Vorbis::Comments object.
87  */
88 static VALUE
89 c_init (VALUE self)
90 {
91         rb_iv_set (self, "items", rb_ary_new ());
92
93         return self;
94 }
95
96 /*
97  * call-seq:
98  *  object.inspect -> string
99  *
100  * Returns the contents of *object* as a string.
101  */
102 static VALUE
103 c_inspect (VALUE self)
104 {
105         VALUE ret;
106         struct RArray *items;
107         int i;
108
109         items = RARRAY (rb_iv_get (self, "items"));
110
111         ret = rb_str_buf_new (128);
112         rb_str_buf_cat (ret, "{", 1);
113
114         for (i = 0; i < items->len; i++) {
115                 struct RArray *pair = RARRAY (items->ptr[i]);
116
117                 if (i)
118                         rb_str_buf_cat (ret, ", ", 2);
119
120                 rb_str_buf_append (ret, rb_inspect (pair->ptr[0]));
121                 rb_str_buf_cat (ret, "=>", 2);
122                 rb_str_buf_append (ret, rb_inspect (pair->ptr[1]));
123         }
124
125         rb_str_buf_cat (ret, "}", 1);
126
127         return ret;
128 }
129
130 /*
131  * call-seq:
132  *  object.clear -> object
133  *
134  * Removes all elements from *object* and returns it.
135  */
136 static VALUE
137 c_clear (VALUE self)
138 {
139         rb_ary_clear (rb_iv_get (self, "items"));
140
141         return self;
142 }
143
144 /*
145  * call-seq:
146  *  object.delete(key) -> string or nil
147  *
148  * If a tag with the specified key exists, that tag is deleted and
149  * the tag's value is returned. Otherwise, +nil+ is returned.
150  */
151 static VALUE
152 c_delete (VALUE self, VALUE key)
153 {
154         VALUE ret = Qnil;
155         struct RArray *items;
156         int i, pos = -1;
157
158         items = RARRAY (rb_iv_get (self, "items"));
159
160         for (i = 0; i < items->len; i++) {
161                 struct RArray *pair = RARRAY (items->ptr[i]);
162                 VALUE tmp;
163
164                 tmp = rb_funcall (pair->ptr[0], id_casecmp, 1, key);
165                 if (tmp == INT2FIX (0)) {
166                         ret = pair->ptr[1];
167                         pos = i;
168                         break;
169                 }
170         }
171
172         if (pos != -1)
173                 rb_ary_delete_at (rb_iv_get (self, "items"), pos);
174
175         return ret;
176 }
177
178 /*
179  * call-seq:
180  *  object.keys -> array
181  *
182  * Returns an array that contains the keys of the tags in *object*.
183  */
184 static VALUE
185 c_keys (VALUE self)
186 {
187         VALUE ret;
188         struct RArray *items;
189         int i;
190
191         items = RARRAY (rb_iv_get (self, "items"));
192         ret = rb_ary_new2 (items->len);
193
194         for (i = 0; i < items->len; i++) {
195                 struct RArray *pair = RARRAY (items->ptr[i]);
196
197                 rb_ary_store (ret, i, pair->ptr[0]);
198         }
199
200         return ret;
201 }
202
203 /*
204  * call-seq:
205  *  object.values -> array
206  *
207  * Returns an array that contains the values of the tags in *object*.
208  */
209 static VALUE
210 c_values (VALUE self)
211 {
212         VALUE ret;
213         struct RArray *items;
214         int i;
215
216         items = RARRAY (rb_iv_get (self, "items"));
217         ret = rb_ary_new2 (items->len);
218
219         for (i = 0; i < items->len; i++) {
220                 struct RArray *pair = RARRAY (items->ptr[i]);
221
222                 rb_ary_store (ret, i, pair->ptr[1]);
223         }
224
225         return ret;
226 }
227
228 /*
229  * call-seq:
230  *  object.length -> integer
231  *
232  * Returns the number of tags in *object*.
233  */
234 static VALUE
235 c_length (VALUE self)
236 {
237         struct RArray *items;
238
239         items = RARRAY (rb_iv_get (self, "items"));
240
241         return LONG2NUM (items->len);
242 }
243
244 /*
245  * call-seq:
246  *  object.empty? -> true or false
247  *
248  * Returns true if *object* is empty or false otherwise.
249  */
250 static VALUE
251 c_get_empty (VALUE self)
252 {
253         struct RArray *items;
254
255         items = RARRAY (rb_iv_get (self, "items"));
256
257         return items->len ? Qfalse : Qtrue;
258 }
259
260 /*
261  * call-seq:
262  *  object[key] -> string
263  *
264  * Returns the value of the tag with the key *key* or +nil+ if the
265  * tag cannot be found.
266  */
267 static VALUE
268 c_aref (VALUE self, VALUE key)
269 {
270         struct RArray *items;
271         int i;
272
273         items = RARRAY (rb_iv_get (self, "items"));
274
275         for (i = 0; i < items->len; i++) {
276                 struct RArray *pair = RARRAY (items->ptr[i]);
277                 VALUE tmp;
278
279                 tmp = rb_funcall (pair->ptr[0], id_casecmp, 1, key);
280                 if (tmp == INT2FIX (0))
281                         return pair->ptr[1];
282         }
283
284         return Qnil;
285 }
286
287 /*
288  * call-seq:
289  *  object[key] = string
290  *
291  * Sets the value of the tag with the key *key* to *string*.
292  */
293 static VALUE
294 c_aset (VALUE self, VALUE key, VALUE value)
295 {
296         VALUE tmp;
297         struct RArray *items;
298         int i;
299
300         items = RARRAY (rb_iv_get (self, "items"));
301
302         for (i = 0; i < items->len; i++) {
303                 struct RArray *pair = RARRAY (items->ptr[i]);
304                 VALUE tmp;
305
306                 tmp = rb_funcall (pair->ptr[0], id_casecmp, 1, key);
307                 if (tmp == INT2FIX (0)) {
308                         rb_funcall (pair->ptr[1], id_replace, 1, value);
309                         return pair->ptr[1];
310                 }
311         }
312
313         tmp = rb_ary_new3 (2, rb_str_dup_frozen (key), value);
314         OBJ_FREEZE (tmp);
315
316         rb_ary_push (rb_iv_get (self, "items"), tmp);
317
318         return value;
319 }
320
321 /*
322  * call-seq:
323  *  object.has_key?(key) -> true or false
324  *
325  * Returns true if a tag exists with the specified key or false
326  * otherwise.
327  */
328 static VALUE
329 c_has_key (VALUE self, VALUE key)
330 {
331         struct RArray *items;
332         int i;
333
334         items = RARRAY (rb_iv_get (self, "items"));
335
336         for (i = 0; i < items->len; i++) {
337                 struct RArray *pair = RARRAY (items->ptr[i]);
338                 VALUE tmp;
339
340                 tmp = rb_funcall (pair->ptr[0], id_casecmp, 1, key);
341                 if (tmp == INT2FIX (0))
342                         return Qtrue;
343         }
344
345         return Qfalse;
346 }
347
348 static VALUE
349 merge_cb (VALUE ar, VALUE self)
350 {
351         struct RArray *pair = RARRAY (ar);
352
353         c_aset (self, pair->ptr[0], pair->ptr[1]);
354
355         return Qnil;
356 }
357
358 /*
359  * call-seq:
360  *  object.merge!(arg) -> object
361  *
362  * Adds the key-value pairs from *arg* to *object*, overwriting existing
363  * values if a key already existed in *object*.
364  *
365  * Note that *arg*'s each method needs to yield key-value pairs for this
366  * to work. This means that e.g. hashes and Ogg::Vorbis::Comments objects
367  * are supported as arguments.
368  */
369 static VALUE
370 c_merge (VALUE self, VALUE arg)
371 {
372         if (!rb_respond_to (arg, id_each))
373                 rb_raise (rb_eArgError, "invalid argument");
374
375         rb_iterate (rb_each, arg, merge_cb, self);
376
377         return self;
378 }
379
380 /*
381  * call-seq:
382  *  object.shift(hash) -> array or nil
383  *
384  * Removes the first key-value pair from *object* and returns it
385  * as the two-item array [key, value].
386  * If *object* is empty, +nil+ is returned.
387  */
388 static VALUE
389 c_shift (VALUE self)
390 {
391         return rb_ary_shift (rb_iv_get (self, "items"));
392 }
393
394 /*
395  * call-seq:
396  *  object <=> other -> -1, 0 or 1
397  *
398  * Compares *object* to *other* and returns -1, 0 or 1 if
399  * *object* is less than, equal or greater than *other*.
400  */
401 static VALUE
402 c_compare (VALUE self, VALUE other)
403 {
404         struct RArray *a, *b;
405         int i, j;
406
407         if (rb_obj_is_kind_of (other, CLASS_OF (self)) != Qtrue)
408                 rb_raise (rb_eArgError, "invalid argument");
409
410         a = RARRAY (rb_iv_get (self, "items"));
411         b = RARRAY (rb_iv_get (other, "items"));
412
413         if (a->len < b->len)
414                 return -1;
415
416         if (b->len < a->len)
417                 return 1;
418
419         for (i = 0; i < a->len; i++) {
420                 struct RArray *aa = RARRAY (a->ptr[i]);
421                 struct RArray *bb = RARRAY (b->ptr[i]);
422
423                 for (j = 0; j < 2; j++) {
424                         VALUE tmp;
425
426                         tmp = rb_funcall (aa->ptr[j], id_compare, 1, bb->ptr[j]);
427                         if (FIX2INT (tmp) != 0)
428                                 return tmp;
429                 }
430         }
431
432         return INT2FIX (0);
433 }
434
435 /*
436  * call-seq:
437  *  object.each { |key, value| block } -> object
438  *
439  * Calls _block_ once for each tag in *object*, passing the key and
440  * value of the tag.
441  * Returns *object*.
442  */
443 static VALUE
444 c_each (VALUE self)
445 {
446         struct RArray *items;
447         int i;
448
449         items = RARRAY (rb_iv_get (self, "items"));
450
451         for (i = 0; i < items->len; i++) {
452                 struct RArray *pair = RARRAY (items->ptr[i]);
453
454                 rb_yield_values (2, pair->ptr[0], pair->ptr[1]);
455         }
456
457         return self;
458 }
459
460 /*
461  * call-seq:
462  *  object.each_key { |key| block } -> object
463  *
464  * Calls _block_ once for each tag in *object*, passing the key
465  * of the tag.
466  * Returns *object*.
467  */
468 static VALUE
469 c_each_key (VALUE self)
470 {
471         struct RArray *items;
472         int i;
473
474         items = RARRAY (rb_iv_get (self, "items"));
475
476         for (i = 0; i < items->len; i++) {
477                 struct RArray *pair = RARRAY (items->ptr[i]);
478
479                 rb_yield (pair->ptr[0]);
480         }
481
482         return self;
483 }
484
485 /*
486  * call-seq:
487  *  object.each_value { |value| block } -> object
488  *
489  * Calls _block_ once for each tag in *object*, passing the value
490  * of the tag. Returns *object*.
491  */
492 static VALUE
493 c_each_value (VALUE self)
494 {
495         struct RArray *items;
496         int i;
497
498         items = RARRAY (rb_iv_get (self, "items"));
499
500         for (i = 0; i < items->len; i++) {
501                 struct RArray *pair = RARRAY (items->ptr[i]);
502
503                 rb_yield (pair->ptr[1]);
504         }
505
506         return self;
507 }
508
509 VALUE
510 Init_Comments (VALUE mVorbis)
511 {
512         VALUE c;
513
514         c = rb_define_class_under (mVorbis, "Comments", rb_cObject);
515
516         rb_define_method (c, "initialize", c_init, 0);
517         rb_define_method (c, "inspect", c_inspect, 0);
518         rb_define_method (c, "clear", c_clear, 0);
519         rb_define_method (c, "delete", c_delete, 1);
520         rb_define_method (c, "length", c_length, 0);
521         rb_define_method (c, "has_key?", c_has_key, 1);
522         rb_define_method (c, "[]", c_aref, 1);
523         rb_define_method (c, "[]=", c_aset, 2);
524         rb_define_method (c, "empty?", c_get_empty, 0);
525         rb_define_method (c, "keys", c_keys, 0);
526         rb_define_method (c, "values", c_values, 0);
527         rb_define_method (c, "merge!", c_merge, 1);
528         rb_define_method (c, "shift", c_shift, 0);
529
530         rb_include_module (c, rb_mComparable);
531         rb_define_method (c, "<=>", c_compare, 1);
532
533         rb_include_module (c, rb_mEnumerable);
534         rb_define_method (c, "each", c_each, 0);
535         rb_define_method (c, "each_key", c_each_key, 0);
536         rb_define_method (c, "each_value", c_each_value, 0);
537
538         rb_define_alias (c, "size", "length");
539         rb_define_alias (c, "each_pair", "each");
540         rb_define_alias (c, "key?", "has_key?");
541         rb_define_alias (c, "include?", "has_key?");
542         rb_define_alias (c, "member?", "has_key?");
543
544         id_casecmp = rb_intern ("casecmp");
545         id_replace = rb_intern ("replace");
546         id_each = rb_intern ("each");
547         id_compare = rb_intern ("<=>");
548
549         return c;
550 }