1#include <psych.h>
2
3VALUE cPsychEmitter;
4static ID id_write;
5static ID id_line_width;
6static ID id_indentation;
7static ID id_canonical;
8
9static void emit(yaml_emitter_t * emitter, yaml_event_t * event)
10{
11    if(!yaml_emitter_emit(emitter, event))
12	rb_raise(rb_eRuntimeError, "%s", emitter->problem);
13}
14
15static int writer(void *ctx, unsigned char *buffer, size_t size)
16{
17    VALUE io = (VALUE)ctx;
18    VALUE str = rb_str_new((const char *)buffer, (long)size);
19    VALUE wrote = rb_funcall(io, id_write, 1, str);
20    return (int)NUM2INT(wrote);
21}
22
23static void dealloc(void * ptr)
24{
25    yaml_emitter_t * emitter;
26
27    emitter = (yaml_emitter_t *)ptr;
28    yaml_emitter_delete(emitter);
29    xfree(emitter);
30}
31
32static VALUE allocate(VALUE klass)
33{
34    yaml_emitter_t * emitter;
35
36    emitter = xmalloc(sizeof(yaml_emitter_t));
37
38    yaml_emitter_initialize(emitter);
39    yaml_emitter_set_unicode(emitter, 1);
40    yaml_emitter_set_indent(emitter, 2);
41
42    return Data_Wrap_Struct(klass, 0, dealloc, emitter);
43}
44
45/* call-seq: Psych::Emitter.new(io, options = Psych::Emitter::OPTIONS)
46 *
47 * Create a new Psych::Emitter that writes to +io+.
48 */
49static VALUE initialize(int argc, VALUE *argv, VALUE self)
50{
51    yaml_emitter_t * emitter;
52    VALUE io, options;
53    VALUE line_width;
54    VALUE indent;
55    VALUE canonical;
56
57    Data_Get_Struct(self, yaml_emitter_t, emitter);
58
59    if (rb_scan_args(argc, argv, "11", &io, &options) == 2) {
60	line_width = rb_funcall(options, id_line_width, 0);
61	indent     = rb_funcall(options, id_indentation, 0);
62	canonical  = rb_funcall(options, id_canonical, 0);
63
64	yaml_emitter_set_width(emitter, NUM2INT(line_width));
65	yaml_emitter_set_indent(emitter, NUM2INT(indent));
66	yaml_emitter_set_canonical(emitter, Qtrue == canonical ? 1 : 0);
67    }
68
69    yaml_emitter_set_output(emitter, writer, (void *)io);
70
71    return self;
72}
73
74/* call-seq: emitter.start_stream(encoding)
75 *
76 * Start a stream emission with +encoding+
77 *
78 * See Psych::Handler#start_stream
79 */
80static VALUE start_stream(VALUE self, VALUE encoding)
81{
82    yaml_emitter_t * emitter;
83    yaml_event_t event;
84    Data_Get_Struct(self, yaml_emitter_t, emitter);
85    Check_Type(encoding, T_FIXNUM);
86
87    yaml_stream_start_event_initialize(&event, (yaml_encoding_t)NUM2INT(encoding));
88
89    emit(emitter, &event);
90
91    return self;
92}
93
94/* call-seq: emitter.end_stream
95 *
96 * End a stream emission
97 *
98 * See Psych::Handler#end_stream
99 */
100static VALUE end_stream(VALUE self)
101{
102    yaml_emitter_t * emitter;
103    yaml_event_t event;
104    Data_Get_Struct(self, yaml_emitter_t, emitter);
105
106    yaml_stream_end_event_initialize(&event);
107
108    emit(emitter, &event);
109
110    return self;
111}
112
113/* call-seq: emitter.start_document(version, tags, implicit)
114 *
115 * Start a document emission with YAML +version+, +tags+, and an +implicit+
116 * start.
117 *
118 * See Psych::Handler#start_document
119 */
120static VALUE start_document(VALUE self, VALUE version, VALUE tags, VALUE imp)
121{
122    yaml_emitter_t * emitter;
123    yaml_tag_directive_t * head = NULL;
124    yaml_tag_directive_t * tail = NULL;
125    yaml_event_t event;
126    yaml_version_directive_t version_directive;
127    Data_Get_Struct(self, yaml_emitter_t, emitter);
128
129
130    Check_Type(version, T_ARRAY);
131
132    if(RARRAY_LEN(version) > 0) {
133	VALUE major = rb_ary_entry(version, (long)0);
134	VALUE minor = rb_ary_entry(version, (long)1);
135
136	version_directive.major = NUM2INT(major);
137	version_directive.minor = NUM2INT(minor);
138    }
139
140    if(RTEST(tags)) {
141	int i = 0;
142#ifdef HAVE_RUBY_ENCODING_H
143	rb_encoding * encoding = rb_utf8_encoding();
144#endif
145
146	Check_Type(tags, T_ARRAY);
147
148	head  = xcalloc((size_t)RARRAY_LEN(tags), sizeof(yaml_tag_directive_t));
149	tail  = head;
150
151	for(i = 0; i < RARRAY_LEN(tags); i++) {
152	    VALUE tuple = RARRAY_PTR(tags)[i];
153	    VALUE name;
154	    VALUE value;
155
156	    Check_Type(tuple, T_ARRAY);
157
158	    if(RARRAY_LEN(tuple) < 2) {
159		xfree(head);
160		rb_raise(rb_eRuntimeError, "tag tuple must be of length 2");
161	    }
162	    name  = RARRAY_PTR(tuple)[0];
163	    value = RARRAY_PTR(tuple)[1];
164#ifdef HAVE_RUBY_ENCODING_H
165	    name = rb_str_export_to_enc(name, encoding);
166	    value = rb_str_export_to_enc(value, encoding);
167#endif
168
169	    tail->handle = (yaml_char_t *)StringValuePtr(name);
170	    tail->prefix = (yaml_char_t *)StringValuePtr(value);
171
172	    tail++;
173	}
174    }
175
176    yaml_document_start_event_initialize(
177	    &event,
178	    (RARRAY_LEN(version) > 0) ? &version_directive : NULL,
179	    head,
180	    tail,
181	    imp ? 1 : 0
182	    );
183
184    emit(emitter, &event);
185
186    if(head) xfree(head);
187
188    return self;
189}
190
191/* call-seq: emitter.end_document(implicit)
192 *
193 * End a document emission with an +implicit+ ending.
194 *
195 * See Psych::Handler#end_document
196 */
197static VALUE end_document(VALUE self, VALUE imp)
198{
199    yaml_emitter_t * emitter;
200    yaml_event_t event;
201    Data_Get_Struct(self, yaml_emitter_t, emitter);
202
203    yaml_document_end_event_initialize(&event, imp ? 1 : 0);
204
205    emit(emitter, &event);
206
207    return self;
208}
209
210/* call-seq: emitter.scalar(value, anchor, tag, plain, quoted, style)
211 *
212 * Emit a scalar with +value+, +anchor+, +tag+, and a +plain+ or +quoted+
213 * string type with +style+.
214 *
215 * See Psych::Handler#scalar
216 */
217static VALUE scalar(
218	VALUE self,
219	VALUE value,
220	VALUE anchor,
221	VALUE tag,
222	VALUE plain,
223	VALUE quoted,
224	VALUE style
225	) {
226    yaml_emitter_t * emitter;
227    yaml_event_t event;
228#ifdef HAVE_RUBY_ENCODING_H
229    rb_encoding *encoding;
230#endif
231    Data_Get_Struct(self, yaml_emitter_t, emitter);
232
233    Check_Type(value, T_STRING);
234
235#ifdef HAVE_RUBY_ENCODING_H
236    encoding = rb_utf8_encoding();
237
238    value = rb_str_export_to_enc(value, encoding);
239
240    if(!NIL_P(anchor)) {
241	Check_Type(anchor, T_STRING);
242	anchor = rb_str_export_to_enc(anchor, encoding);
243    }
244
245    if(!NIL_P(tag)) {
246	Check_Type(tag, T_STRING);
247	tag = rb_str_export_to_enc(tag, encoding);
248    }
249#endif
250
251    yaml_scalar_event_initialize(
252	    &event,
253	    (yaml_char_t *)(NIL_P(anchor) ? NULL : StringValuePtr(anchor)),
254	    (yaml_char_t *)(NIL_P(tag) ? NULL : StringValuePtr(tag)),
255	    (yaml_char_t*)StringValuePtr(value),
256	    (int)RSTRING_LEN(value),
257	    plain ? 1 : 0,
258	    quoted ? 1 : 0,
259	    (yaml_scalar_style_t)NUM2INT(style)
260	    );
261
262    emit(emitter, &event);
263
264    return self;
265}
266
267/* call-seq: emitter.start_sequence(anchor, tag, implicit, style)
268 *
269 * Start emitting a sequence with +anchor+, a +tag+, +implicit+ sequence
270 * start and end, along with +style+.
271 *
272 * See Psych::Handler#start_sequence
273 */
274static VALUE start_sequence(
275	VALUE self,
276	VALUE anchor,
277	VALUE tag,
278	VALUE implicit,
279	VALUE style
280	) {
281    yaml_emitter_t * emitter;
282    yaml_event_t event;
283
284#ifdef HAVE_RUBY_ENCODING_H
285    rb_encoding * encoding = rb_utf8_encoding();
286
287    if(!NIL_P(anchor)) {
288	Check_Type(anchor, T_STRING);
289	anchor = rb_str_export_to_enc(anchor, encoding);
290    }
291
292    if(!NIL_P(tag)) {
293	Check_Type(tag, T_STRING);
294	tag = rb_str_export_to_enc(tag, encoding);
295    }
296#endif
297
298    Data_Get_Struct(self, yaml_emitter_t, emitter);
299
300    yaml_sequence_start_event_initialize(
301	    &event,
302	    (yaml_char_t *)(NIL_P(anchor) ? NULL : StringValuePtr(anchor)),
303	    (yaml_char_t *)(NIL_P(tag) ? NULL : StringValuePtr(tag)),
304	    implicit ? 1 : 0,
305	    (yaml_sequence_style_t)NUM2INT(style)
306	    );
307
308    emit(emitter, &event);
309
310    return self;
311}
312
313/* call-seq: emitter.end_sequence
314 *
315 * End sequence emission.
316 *
317 * See Psych::Handler#end_sequence
318 */
319static VALUE end_sequence(VALUE self)
320{
321    yaml_emitter_t * emitter;
322    yaml_event_t event;
323    Data_Get_Struct(self, yaml_emitter_t, emitter);
324
325    yaml_sequence_end_event_initialize(&event);
326
327    emit(emitter, &event);
328
329    return self;
330}
331
332/* call-seq: emitter.start_mapping(anchor, tag, implicit, style)
333 *
334 * Start emitting a YAML map with +anchor+, +tag+, an +implicit+ start
335 * and end, and +style+.
336 *
337 * See Psych::Handler#start_mapping
338 */
339static VALUE start_mapping(
340	VALUE self,
341	VALUE anchor,
342	VALUE tag,
343	VALUE implicit,
344	VALUE style
345	) {
346    yaml_emitter_t * emitter;
347    yaml_event_t event;
348#ifdef HAVE_RUBY_ENCODING_H
349    rb_encoding *encoding;
350#endif
351    Data_Get_Struct(self, yaml_emitter_t, emitter);
352
353#ifdef HAVE_RUBY_ENCODING_H
354    encoding = rb_utf8_encoding();
355
356    if(!NIL_P(anchor)) {
357	Check_Type(anchor, T_STRING);
358	anchor = rb_str_export_to_enc(anchor, encoding);
359    }
360
361    if(!NIL_P(tag)) {
362	Check_Type(tag, T_STRING);
363	tag = rb_str_export_to_enc(tag, encoding);
364    }
365#endif
366
367    yaml_mapping_start_event_initialize(
368	    &event,
369	    (yaml_char_t *)(NIL_P(anchor) ? NULL : StringValuePtr(anchor)),
370	    (yaml_char_t *)(NIL_P(tag) ? NULL : StringValuePtr(tag)),
371	    implicit ? 1 : 0,
372	    (yaml_mapping_style_t)NUM2INT(style)
373	    );
374
375    emit(emitter, &event);
376
377    return self;
378}
379
380/* call-seq: emitter.end_mapping
381 *
382 * Emit the end of a mapping.
383 *
384 * See Psych::Handler#end_mapping
385 */
386static VALUE end_mapping(VALUE self)
387{
388    yaml_emitter_t * emitter;
389    yaml_event_t event;
390    Data_Get_Struct(self, yaml_emitter_t, emitter);
391
392    yaml_mapping_end_event_initialize(&event);
393
394    emit(emitter, &event);
395
396    return self;
397}
398
399/* call-seq: emitter.alias(anchor)
400 *
401 * Emit an alias with +anchor+.
402 *
403 * See Psych::Handler#alias
404 */
405static VALUE alias(VALUE self, VALUE anchor)
406{
407    yaml_emitter_t * emitter;
408    yaml_event_t event;
409    Data_Get_Struct(self, yaml_emitter_t, emitter);
410
411#ifdef HAVE_RUBY_ENCODING_H
412    if(!NIL_P(anchor)) {
413	Check_Type(anchor, T_STRING);
414	anchor = rb_str_export_to_enc(anchor, rb_utf8_encoding());
415    }
416#endif
417
418    yaml_alias_event_initialize(
419	    &event,
420	    (yaml_char_t *)(NIL_P(anchor) ? NULL : StringValuePtr(anchor))
421	    );
422
423    emit(emitter, &event);
424
425    return self;
426}
427
428/* call-seq: emitter.canonical = true
429 *
430 * Set the output style to canonical, or not.
431 */
432static VALUE set_canonical(VALUE self, VALUE style)
433{
434    yaml_emitter_t * emitter;
435    Data_Get_Struct(self, yaml_emitter_t, emitter);
436
437    yaml_emitter_set_canonical(emitter, Qtrue == style ? 1 : 0);
438
439    return style;
440}
441
442/* call-seq: emitter.canonical
443 *
444 * Get the output style, canonical or not.
445 */
446static VALUE canonical(VALUE self)
447{
448    yaml_emitter_t * emitter;
449    Data_Get_Struct(self, yaml_emitter_t, emitter);
450
451    return (emitter->canonical == 0) ? Qfalse : Qtrue;
452}
453
454/* call-seq: emitter.indentation = level
455 *
456 * Set the indentation level to +level+.  The level must be less than 10 and
457 * greater than 1.
458 */
459static VALUE set_indentation(VALUE self, VALUE level)
460{
461    yaml_emitter_t * emitter;
462    Data_Get_Struct(self, yaml_emitter_t, emitter);
463
464    yaml_emitter_set_indent(emitter, NUM2INT(level));
465
466    return level;
467}
468
469/* call-seq: emitter.indentation
470 *
471 * Get the indentation level.
472 */
473static VALUE indentation(VALUE self)
474{
475    yaml_emitter_t * emitter;
476    Data_Get_Struct(self, yaml_emitter_t, emitter);
477
478    return INT2NUM(emitter->best_indent);
479}
480
481/* call-seq: emitter.line_width
482 *
483 * Get the preferred line width.
484 */
485static VALUE line_width(VALUE self)
486{
487    yaml_emitter_t * emitter;
488    Data_Get_Struct(self, yaml_emitter_t, emitter);
489
490    return INT2NUM(emitter->best_width);
491}
492
493/* call-seq: emitter.line_width = width
494 *
495 * Set the preferred line with to +width+.
496 */
497static VALUE set_line_width(VALUE self, VALUE width)
498{
499    yaml_emitter_t * emitter;
500    Data_Get_Struct(self, yaml_emitter_t, emitter);
501
502    yaml_emitter_set_width(emitter, NUM2INT(width));
503
504    return width;
505}
506
507void Init_psych_emitter()
508{
509    VALUE psych     = rb_define_module("Psych");
510    VALUE handler   = rb_define_class_under(psych, "Handler", rb_cObject);
511    cPsychEmitter   = rb_define_class_under(psych, "Emitter", handler);
512
513    rb_define_alloc_func(cPsychEmitter, allocate);
514
515    rb_define_method(cPsychEmitter, "initialize", initialize, -1);
516    rb_define_method(cPsychEmitter, "start_stream", start_stream, 1);
517    rb_define_method(cPsychEmitter, "end_stream", end_stream, 0);
518    rb_define_method(cPsychEmitter, "start_document", start_document, 3);
519    rb_define_method(cPsychEmitter, "end_document", end_document, 1);
520    rb_define_method(cPsychEmitter, "scalar", scalar, 6);
521    rb_define_method(cPsychEmitter, "start_sequence", start_sequence, 4);
522    rb_define_method(cPsychEmitter, "end_sequence", end_sequence, 0);
523    rb_define_method(cPsychEmitter, "start_mapping", start_mapping, 4);
524    rb_define_method(cPsychEmitter, "end_mapping", end_mapping, 0);
525    rb_define_method(cPsychEmitter, "alias", alias, 1);
526    rb_define_method(cPsychEmitter, "canonical", canonical, 0);
527    rb_define_method(cPsychEmitter, "canonical=", set_canonical, 1);
528    rb_define_method(cPsychEmitter, "indentation", indentation, 0);
529    rb_define_method(cPsychEmitter, "indentation=", set_indentation, 1);
530    rb_define_method(cPsychEmitter, "line_width", line_width, 0);
531    rb_define_method(cPsychEmitter, "line_width=", set_line_width, 1);
532
533    id_write       = rb_intern("write");
534    id_line_width  = rb_intern("line_width");
535    id_indentation = rb_intern("indentation");
536    id_canonical   = rb_intern("canonical");
537}
538/* vim: set noet sws=4 sw=4: */
539