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