fb3ae82f653d727d751cc9975626caabfb1b5f8c
[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_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 int
349 merge_cb (VALUE key, VALUE value, VALUE self)
350 {
351         c_aset (self, key, value);
352
353         return ST_CONTINUE;
354 }
355
356 /*
357  * call-seq:
358  *  object.merge!(hash) -> object
359  *
360  * Adds the key-value pairs from *hash* to *object*, overwriting existing
361  * values if a key already existed in *object*.
362  */
363 static VALUE
364 c_merge (VALUE self, VALUE hash)
365 {
366         Check_Type (hash, T_HASH);
367
368         rb_hash_foreach (hash, merge_cb, self);
369
370         return self;
371 }
372
373 /*
374  * call-seq:
375  *  object.shift(hash) -> array or nil
376  *
377  * Removes the first key-value pair from *object* and returns it
378  * as the two-item array [key, value].
379  * If *object* is empty, +nil+ is returned.
380  */
381 static VALUE
382 c_shift (VALUE self)
383 {
384         return rb_ary_shift (rb_iv_get (self, "items"));
385 }
386
387 /*
388  * call-seq:
389  *  object <=> other -> -1, 0 or 1
390  *
391  * Compares *object* to *other* and returns -1, 0 or 1 if
392  * *object* is less than, equal or greater than *other*.
393  */
394 static VALUE
395 c_compare (VALUE self, VALUE other)
396 {
397         struct RArray *a, *b;
398         int i, j;
399
400         if (rb_obj_is_kind_of (other, CLASS_OF (self)) != Qtrue)
401                 rb_raise (rb_eArgError, "invalid argument");
402
403         a = RARRAY (rb_iv_get (self, "items"));
404         b = RARRAY (rb_iv_get (other, "items"));
405
406         if (a->len < b->len)
407                 return -1;
408
409         if (b->len < a->len)
410                 return 1;
411
412         for (i = 0; i < a->len; i++) {
413                 struct RArray *aa = RARRAY (a->ptr[i]);
414                 struct RArray *bb = RARRAY (b->ptr[i]);
415
416                 for (j = 0; j < 2; j++) {
417                         VALUE tmp;
418
419                         tmp = rb_funcall (aa->ptr[j], id_compare, 1, bb->ptr[j]);
420                         if (FIX2INT (tmp) != 0)
421                                 return tmp;
422                 }
423         }
424
425         return INT2FIX (0);
426 }
427
428 /*
429  * call-seq:
430  *  object.each { |key, value| block } -> object
431  *
432  * Calls _block_ once for each tag in *object*, passing the key and
433  * value of the tag.
434  * Returns *object*.
435  */
436 static VALUE
437 c_each (VALUE self)
438 {
439         struct RArray *items;
440         int i;
441
442         items = RARRAY (rb_iv_get (self, "items"));
443
444         for (i = 0; i < items->len; i++) {
445                 struct RArray *pair = RARRAY (items->ptr[i]);
446
447                 rb_yield_values (2, pair->ptr[0], pair->ptr[1]);
448         }
449
450         return self;
451 }
452
453 /*
454  * call-seq:
455  *  object.each_key { |key| block } -> object
456  *
457  * Calls _block_ once for each tag in *object*, passing the key
458  * of the tag.
459  * Returns *object*.
460  */
461 static VALUE
462 c_each_key (VALUE self)
463 {
464         struct RArray *items;
465         int i;
466
467         items = RARRAY (rb_iv_get (self, "items"));
468
469         for (i = 0; i < items->len; i++) {
470                 struct RArray *pair = RARRAY (items->ptr[i]);
471
472                 rb_yield (pair->ptr[0]);
473         }
474
475         return self;
476 }
477
478 /*
479  * call-seq:
480  *  object.each_value { |value| block } -> object
481  *
482  * Calls _block_ once for each tag in *object*, passing the value
483  * of the tag. Returns *object*.
484  */
485 static VALUE
486 c_each_value (VALUE self)
487 {
488         struct RArray *items;
489         int i;
490
491         items = RARRAY (rb_iv_get (self, "items"));
492
493         for (i = 0; i < items->len; i++) {
494                 struct RArray *pair = RARRAY (items->ptr[i]);
495
496                 rb_yield (pair->ptr[1]);
497         }
498
499         return self;
500 }
501
502 VALUE
503 Init_Comments (VALUE mVorbis)
504 {
505         VALUE c;
506
507         c = rb_define_class_under (mVorbis, "Comments", rb_cObject);
508
509         rb_define_method (c, "initialize", c_init, 0);
510         rb_define_method (c, "inspect", c_inspect, 0);
511         rb_define_method (c, "clear", c_clear, 0);
512         rb_define_method (c, "delete", c_delete, 1);
513         rb_define_method (c, "length", c_length, 0);
514         rb_define_method (c, "has_key?", c_has_key, 1);
515         rb_define_method (c, "[]", c_aref, 1);
516         rb_define_method (c, "[]=", c_aset, 2);
517         rb_define_method (c, "empty?", c_get_empty, 0);
518         rb_define_method (c, "keys", c_keys, 0);
519         rb_define_method (c, "values", c_values, 0);
520         rb_define_method (c, "merge!", c_merge, 1);
521         rb_define_method (c, "shift", c_shift, 0);
522
523         rb_include_module (c, rb_mComparable);
524         rb_define_method (c, "<=>", c_compare, 1);
525
526         rb_include_module (c, rb_mEnumerable);
527         rb_define_method (c, "each", c_each, 0);
528         rb_define_method (c, "each_key", c_each_key, 0);
529         rb_define_method (c, "each_value", c_each_value, 0);
530
531         rb_define_alias (c, "size", "length");
532         rb_define_alias (c, "each_pair", "each");
533         rb_define_alias (c, "key?", "has_key?");
534         rb_define_alias (c, "include?", "has_key?");
535         rb_define_alias (c, "member?", "has_key?");
536
537         id_casecmp = rb_intern ("casecmp");
538         id_replace = rb_intern ("replace");
539         id_compare = rb_intern ("<=>");
540
541         return c;
542 }