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