brianmario-yajl-ruby 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.3 (Jun 7th, 2009)
4
+ * The IO parameter for Yajl::Encode#encode is now optional, and accepts a block
5
+ ** it will return the resulting JSON string if no IO is passed to stream to
6
+ ** if a block is passed, it will call and pass it the resulting JSON string
7
+ * Yajl::Parser#parse can now parse from a String as well as an IO
8
+ * Added and updated lot of in-code documentation.
9
+ ** all the C code exposed to Ruby should now have comments
10
+ * Added :symbolize_keys option to the Yajl::Parser class, which defaults to true.
11
+ ** Having this option enabled has shown around an 18% speedup in parsing time according to my benchmarks
12
+
3
13
  ## 0.5.2 (May 30th, 2009)
4
14
  * Added class helper methods Yajl::Encoder.encode(obj, io) and Yajl::Parser.parse(io)
5
15
  * added tests for the above
data/README.rdoc CHANGED
@@ -142,8 +142,8 @@ Some ideas are:
142
142
  * a Rails plugin (http://github.com/technoweenie/yajl-rails)
143
143
  * builtin Rails 3 support?
144
144
  * Rack middleware (ideally the JSON body could be handed to the parser while it's still being received)
145
- * use with ohai
146
- * JSON API clients
145
+ * use with ohai (http://github.com/brianmario/ohai)
146
+ * JSON API clients (http://github.com/brianmario/crack, http://github.com/brianmario/freckle-api)
147
147
  * Patch Marshal#load and Marshal#dump to use JSON? ;)
148
148
  * etc...
149
149
 
@@ -214,4 +214,5 @@ I've had a lot of inspiration, and a lot of help. Thanks to everyone who's been
214
214
  * Tom Smith - http://github.com/rtomsmith - pointer-hacking help
215
215
  * Rick http://github.com/technoweenie - for making an ActiveSupport patch with support for this library and teasing me that it might go into Rails 3. You sure lit a fire under my ass and I got a ton of work done because of it! :)
216
216
  * The entire Github Crew - http://github.com/ - my inspiration, time spent writing this, finding Yajl, So many-MANY other things wouldn't have been possible without this awesome service. I owe you guys some whiskey at Kilowatt.
217
- * benburkert - http://github.com/benburkert
217
+ * benburkert - http://github.com/benburkert
218
+ * Aman Gupta - http://github.com/tmm1 - tons of suggestions and inspiration for the most recent features, and hopefully more to come ;)
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :minor: 5
3
- :patch: 2
4
2
  :major: 0
3
+ :minor: 5
4
+ :patch: 3
data/benchmark/encode.rb CHANGED
@@ -15,11 +15,18 @@ json.close
15
15
  times = ARGV[1] ? ARGV[1].to_i : 1
16
16
  puts "Starting benchmark encoding #{filename} #{times} times\n\n"
17
17
  Benchmark.bm { |x|
18
- encoder = Yajl::Encoder.new
18
+ io_encoder = Yajl::Encoder.new
19
19
  x.report {
20
- puts "Yajl::Encoder#encode"
20
+ puts "Yajl::Encoder#encode (to an IO)"
21
21
  times.times {
22
- encoder.encode(hash, StringIO.new)
22
+ io_encoder.encode(hash, StringIO.new)
23
+ }
24
+ }
25
+ string_encoder = Yajl::Encoder.new
26
+ x.report {
27
+ puts "Yajl::Encoder#encode (to a String)"
28
+ times.times {
29
+ output = string_encoder.encode(hash)
23
30
  }
24
31
  }
25
32
  x.report {
data/benchmark/parse.rb CHANGED
@@ -15,12 +15,20 @@ json.rewind
15
15
  times = ARGV[1] ? ARGV[1].to_i : 1
16
16
  puts "Starting benchmark parsing #{File.size(filename)} bytes of JSON data #{times} times\n\n"
17
17
  Benchmark.bm { |x|
18
- parser = Yajl::Parser.new
18
+ io_parser = Yajl::Parser.new
19
19
  x.report {
20
- puts "Yajl::Parser#parse"
20
+ puts "Yajl::Parser#parse (from an IO)"
21
21
  times.times {
22
22
  json.rewind
23
- parser.parse(json)
23
+ io_parser.parse(json)
24
+ }
25
+ }
26
+ string_parser = Yajl::Parser.new
27
+ x.report {
28
+ puts "Yajl::Parser#parse (from a String)"
29
+ times.times {
30
+ json.rewind
31
+ string_parser.parse(json.read)
24
32
  }
25
33
  }
26
34
  x.report {
data/ext/yajl_ext.c CHANGED
@@ -11,6 +11,14 @@ inline void yajl_check_and_fire_callback(void * ctx) {
11
11
  if (len == 1 && wrapper->nestedArrayLevel == 0 && wrapper->nestedHashLevel == 0) {
12
12
  rb_funcall(wrapper->parse_complete_callback, intern_call, 1, rb_ary_pop(wrapper->builderStack));
13
13
  }
14
+ } else {
15
+ int len = RARRAY_LEN(wrapper->builderStack);
16
+ if (len == 1 && wrapper->nestedArrayLevel == 0 && wrapper->nestedHashLevel == 0) {
17
+ wrapper->objectsFound++;
18
+ if (wrapper->objectsFound > 1) {
19
+ rb_raise(cParseError, "%s", "Found multiple JSON objects in the stream but no block or the on_parse_complete callback was assigned to handle them.");
20
+ }
21
+ }
14
22
  }
15
23
  }
16
24
 
@@ -36,6 +44,7 @@ inline void yajl_set_static_value(void * ctx, VALUE val) {
36
44
  rb_ary_push(wrapper->builderStack, val);
37
45
  break;
38
46
  case T_STRING:
47
+ case T_SYMBOL:
39
48
  hash = rb_ary_entry(wrapper->builderStack, len-2);
40
49
  if (TYPE(hash) == T_HASH) {
41
50
  rb_hash_aset(hash, lastEntry, val);
@@ -58,11 +67,13 @@ void yajl_encode_part(yajl_gen hand, VALUE obj, VALUE io) {
58
67
  const unsigned char * buffer;
59
68
  unsigned int len;
60
69
 
61
- yajl_gen_get_buf(hand, &buffer, &len);
62
- if (len >= WRITE_BUFSIZE) {
63
- outBuff = rb_str_new((const char *)buffer, len);
64
- rb_io_write(io, outBuff);
65
- yajl_gen_clear(hand);
70
+ if (io != Qnil) {
71
+ yajl_gen_get_buf(hand, &buffer, &len);
72
+ if (len >= WRITE_BUFSIZE) {
73
+ outBuff = rb_str_new((const char *)buffer, len);
74
+ rb_io_write(io, outBuff);
75
+ yajl_gen_clear(hand);
76
+ }
66
77
  }
67
78
 
68
79
  switch (TYPE(obj)) {
@@ -126,6 +137,18 @@ void yajl_parser_wrapper_mark(void * wrapper) {
126
137
  rb_gc_mark(w->parse_complete_callback);
127
138
  }
128
139
 
140
+ void yajl_parse_chunk(const unsigned char * chunk, unsigned int len, yajl_handle parser) {
141
+ yajl_status stat;
142
+
143
+ stat = yajl_parse(parser, chunk, len);
144
+
145
+ if (stat != yajl_status_ok && stat != yajl_status_insufficient_data) {
146
+ unsigned char * str = yajl_get_error(parser, 1, chunk, len);
147
+ rb_raise(cParseError, "%s", (const char *) str);
148
+ yajl_free_error(parser, str);
149
+ }
150
+ }
151
+
129
152
  // YAJL Callbacks
130
153
  static int yajl_found_null(void * ctx) {
131
154
  yajl_set_static_value(ctx, Qnil);
@@ -161,7 +184,18 @@ static int yajl_found_string(void * ctx, const unsigned char * stringVal, unsign
161
184
  }
162
185
 
163
186
  static int yajl_found_hash_key(void * ctx, const unsigned char * stringVal, unsigned int stringLen) {
164
- yajl_set_static_value(ctx, rb_str_new((const char *)stringVal, stringLen));
187
+ struct yajl_parser_wrapper * wrapper;
188
+ GetParser((VALUE)ctx, wrapper);
189
+
190
+ if (wrapper->symbolizeKeys) {
191
+ char keyStr[stringLen];
192
+ ID key;
193
+ sprintf(keyStr, "%.*s", stringLen, stringVal);
194
+ key = rb_intern(keyStr);
195
+ yajl_set_static_value(ctx, ID2SYM(key));
196
+ } else {
197
+ yajl_set_static_value(ctx, rb_str_new((const char *)stringVal, stringLen));
198
+ }
165
199
  yajl_check_and_fire_callback(ctx);
166
200
  return 1;
167
201
  }
@@ -205,14 +239,30 @@ static int yajl_found_end_array(void * ctx) {
205
239
  }
206
240
 
207
241
 
208
- /** Ruby Interface */
242
+ // Ruby Interface
209
243
 
210
- // Yajl::Parser
244
+ /*
245
+ * Document-class: Yajl::Parser
246
+ *
247
+ * This class contains methods for parsing JSON directly from an IO object.
248
+ * The only basic requirment currently is that the IO object respond to #read(len) and #eof?
249
+ * The IO is parsed until a complete JSON object has been read and a ruby object will be returned.
250
+ */
251
+
252
+ /*
253
+ * Document-method: new
254
+ *
255
+ * call-seq: new([:allow_comments => false, :check_utf8 => false])
256
+ *
257
+ * :allow_comments will turn on/off the check for comments inside the JSON stream.
258
+ *
259
+ * :check_utf8 will validate UTF8 characters found in the JSON stream.
260
+ */
211
261
  static VALUE rb_yajl_parser_new(int argc, VALUE * argv, VALUE klass) {
212
262
  struct yajl_parser_wrapper * wrapper;
213
263
  yajl_parser_config cfg;
214
264
  VALUE opts, obj;
215
- int allowComments = 1, checkUTF8 = 1;
265
+ int allowComments = 1, checkUTF8 = 1, symbolizeKeys = 1;
216
266
 
217
267
  // Scan off config vars
218
268
  if (rb_scan_args(argc, argv, "01", &opts) == 1) {
@@ -224,6 +274,9 @@ static VALUE rb_yajl_parser_new(int argc, VALUE * argv, VALUE klass) {
224
274
  if (rb_hash_aref(opts, ID2SYM(sym_check_utf8)) == Qfalse) {
225
275
  checkUTF8 = 0;
226
276
  }
277
+ if (rb_hash_aref(opts, ID2SYM(sym_symbolize_keys)) == Qfalse) {
278
+ symbolizeKeys = 0;
279
+ }
227
280
  }
228
281
  cfg = (yajl_parser_config){allowComments, checkUTF8};
229
282
 
@@ -231,44 +284,76 @@ static VALUE rb_yajl_parser_new(int argc, VALUE * argv, VALUE klass) {
231
284
  wrapper->parser = yajl_alloc(&callbacks, &cfg, NULL, (void *)obj);
232
285
  wrapper->nestedArrayLevel = 0;
233
286
  wrapper->nestedHashLevel = 0;
287
+ wrapper->objectsFound = 0;
288
+ wrapper->symbolizeKeys = symbolizeKeys;
234
289
  wrapper->builderStack = rb_ary_new();
235
290
  wrapper->parse_complete_callback = Qnil;
236
291
  rb_obj_call_init(obj, 0, 0);
237
292
  return obj;
238
293
  }
239
294
 
295
+ /*
296
+ * Document-method: initialize
297
+ *
298
+ * call-seq: initialize([:allow_comments => false, :check_utf8 => false])
299
+ *
300
+ * :allow_comments will turn on/off the check for comments inside the JSON stream.
301
+ *
302
+ * :check_utf8 will validate UTF8 characters found in the JSON stream.
303
+ */
240
304
  static VALUE rb_yajl_parser_init(int argc, VALUE * argv, VALUE self) {
241
305
  return self;
242
306
  }
243
307
 
308
+ /*
309
+ * Document-method: parse
310
+ *
311
+ * call-seq:
312
+ * parse(input, buffer_size=8092)
313
+ * parse(input, buffer_size=8092) { |obj| ... }
314
+ *
315
+ * +input+ can either be a string or an IO to parse JSON from
316
+ *
317
+ * +buffer_size+ is the size of chunk that will be parsed off the input (if it's an IO) for each loop of the parsing process.
318
+ * 8092 is a good balance between the different types of streams (off disk, off a socket, etc...), but this option
319
+ * is here so the caller can better tune their parsing depending on the type of stream being passed.
320
+ * A larger read buffer will perform better for files off disk, where as a smaller size may be more efficient for
321
+ * reading off of a socket directly.
322
+ *
323
+ * If a block was passed, it's called when an object has been parsed off the stream. This is especially
324
+ * usefull when parsing a stream of multiple JSON objects.
325
+ *
326
+ * NOTE: you can optionally assign the +on_parse_complete+ callback, and it will be called the same way the optional
327
+ * block is for this method.
328
+ */
244
329
  static VALUE rb_yajl_parser_parse(int argc, VALUE * argv, VALUE self) {
245
- struct yajl_parser_wrapper * wrapper;
246
330
  yajl_status stat;
247
- VALUE parsed, rbufsize, io;
331
+ struct yajl_parser_wrapper * wrapper;
332
+ VALUE parsed, rbufsize, input, blk;
248
333
 
249
334
  GetParser(self, wrapper);
250
335
  parsed = rb_str_new2("");
251
336
 
252
337
  // setup our parameters
253
- rb_scan_args(argc, argv, "11", &io, &rbufsize);
338
+ rb_scan_args(argc, argv, "11&", &input, &rbufsize, &blk);
254
339
  if (NIL_P(rbufsize)) {
255
340
  rbufsize = INT2FIX(READ_BUFSIZE);
256
341
  } else {
257
342
  Check_Type(rbufsize, T_FIXNUM);
258
343
  }
344
+ if (!NIL_P(blk)) {
345
+ rb_yajl_set_complete_cb(self, blk);
346
+ }
259
347
 
260
- // now parse from the IO
261
- while (rb_funcall(io, intern_eof, 0) != Qtrue) {
262
- rb_funcall(io, intern_io_read, 2, rbufsize, parsed);
263
-
264
- stat = yajl_parse(wrapper->parser, (const unsigned char *)RSTRING_PTR(parsed), RSTRING_LEN(parsed));
265
-
266
- if (stat != yajl_status_ok && stat != yajl_status_insufficient_data) {
267
- unsigned char * str = yajl_get_error(wrapper->parser, 1, (const unsigned char *)RSTRING_PTR(parsed), RSTRING_LEN(parsed));
268
- rb_raise(cParseError, "%s", (const char *) str);
269
- yajl_free_error(wrapper->parser, str);
270
- break;
348
+ if (TYPE(input) == T_STRING) {
349
+ yajl_parse_chunk((const unsigned char *)RSTRING_PTR(input), RSTRING_LEN(input), wrapper->parser);
350
+ } else if (rb_respond_to(input, intern_eof)) {
351
+ while (rb_funcall(input, intern_eof, 0) != Qtrue) {
352
+ rb_funcall(input, intern_io_read, 2, rbufsize, parsed);
353
+ yajl_parse_chunk((const unsigned char *)RSTRING_PTR(parsed), RSTRING_LEN(parsed), wrapper->parser);
271
354
  }
355
+ } else {
356
+ rb_raise(cParseError, "input must be a string or IO");
272
357
  }
273
358
 
274
359
  // parse any remaining buffered data
@@ -282,9 +367,19 @@ static VALUE rb_yajl_parser_parse(int argc, VALUE * argv, VALUE self) {
282
367
  return rb_ary_pop(wrapper->builderStack);
283
368
  }
284
369
 
370
+ /*
371
+ * Document-method: parse_chunk
372
+ *
373
+ * call-seq: parse_chunk(string_chunk)
374
+ *
375
+ * +string_chunk+ can be a partial or full JSON string to push on the parser.
376
+ *
377
+ * This method will throw an exception if the +on_parse_complete+ callback hasn't been assigned yet.
378
+ * The +on_parse_complete+ callback assignment is required so the user can handle objects that have been
379
+ * parsed off the stream as they're found.
380
+ */
285
381
  static VALUE rb_yajl_parser_parse_chunk(VALUE self, VALUE chunk) {
286
382
  struct yajl_parser_wrapper * wrapper;
287
- yajl_status stat;
288
383
 
289
384
  GetParser(self, wrapper);
290
385
  if (NIL_P(chunk)) {
@@ -293,12 +388,7 @@ static VALUE rb_yajl_parser_parse_chunk(VALUE self, VALUE chunk) {
293
388
  }
294
389
 
295
390
  if (wrapper->parse_complete_callback != Qnil) {
296
- stat = yajl_parse(wrapper->parser, (const unsigned char *)RSTRING_PTR(chunk), RSTRING_LEN(chunk));
297
- if (stat != yajl_status_ok && stat != yajl_status_insufficient_data) {
298
- unsigned char * str = yajl_get_error(wrapper->parser, 1, (const unsigned char *)RSTRING_PTR(chunk), RSTRING_LEN(chunk));
299
- rb_raise(cParseError, "%s", (const char *) str);
300
- yajl_free_error(wrapper->parser, str);
301
- }
391
+ yajl_parse_chunk((const unsigned char *)RSTRING_PTR(chunk), RSTRING_LEN(chunk), wrapper->parser);
302
392
  } else {
303
393
  rb_raise(cParseError, "The on_parse_complete callback isn't setup, parsing useless.");
304
394
  }
@@ -306,6 +396,15 @@ static VALUE rb_yajl_parser_parse_chunk(VALUE self, VALUE chunk) {
306
396
  return Qnil;
307
397
  }
308
398
 
399
+ /*
400
+ * Document-method: on_parse_complete=
401
+ *
402
+ * call-seq: on_parse_complete = Proc.new { |obj| ... }
403
+ *
404
+ * This callback setter allows you to pass a Proc/lambda or any other object that response to #call.
405
+ *
406
+ * It will pass a single parameter, the ruby object built from the last parsed JSON object
407
+ */
309
408
  static VALUE rb_yajl_set_complete_cb(VALUE self, VALUE callback) {
310
409
  struct yajl_parser_wrapper * wrapper;
311
410
  GetParser(self, wrapper);
@@ -313,7 +412,23 @@ static VALUE rb_yajl_set_complete_cb(VALUE self, VALUE callback) {
313
412
  return Qnil;
314
413
  }
315
414
 
316
- // Yajl::Encoder
415
+ /*
416
+ * Document-class: Yajl::Encoder
417
+ *
418
+ * This class contains methods for encoding a Ruby object into JSON, streaming it's output into an IO object.
419
+ * The IO object need only respond to #write(str)
420
+ * The JSON stream created is written to the IO in chunks, as it's being created.
421
+ */
422
+
423
+ /*
424
+ * Document-method: new
425
+ *
426
+ * call-seq: new([:pretty => false[, :indent => ' ']])
427
+ *
428
+ * :pretty will enable/disable beautifying or "pretty priting" the output string.
429
+ *
430
+ * :indent is the character(s) used to indent the output string.
431
+ */
317
432
  static VALUE rb_yajl_encoder_new(int argc, VALUE * argv, VALUE klass) {
318
433
  yajl_gen_config cfg;
319
434
  yajl_gen encoder;
@@ -342,27 +457,61 @@ static VALUE rb_yajl_encoder_new(int argc, VALUE * argv, VALUE klass) {
342
457
  return obj;
343
458
  }
344
459
 
460
+ /*
461
+ * Document-method: initialize
462
+ *
463
+ * call-seq: initialize([:pretty => false[, :indent => ' ']])
464
+ *
465
+ * :pretty will enable/disable beautifying or "pretty priting" the output string.
466
+ *
467
+ * :indent is the character(s) used to indent the output string.
468
+ */
345
469
  static VALUE rb_yajl_encoder_init(int argc, VALUE * argv, VALUE self) {
346
470
  return self;
347
471
  }
348
472
 
349
- static VALUE rb_yajl_encoder_encode(VALUE self, VALUE obj, VALUE io) {
473
+ /*
474
+ * Document-method: encode
475
+ *
476
+ * call-seq: encode(obj[, io[, &block]])
477
+ *
478
+ * +obj+ is the Ruby object to encode to JSON
479
+ *
480
+ * +io+ is an optional IO used to stream the encoded JSON string to.
481
+ * If +io+ isn't specified, this method will return the resulting JSON string. If +io+ is specified, this method returns nil
482
+ *
483
+ * If an optional block is passed, it's called when encoding is complete and passed the resulting JSON string
484
+ *
485
+ * It should be noted that you can reuse an instance of this class to continue encoding multiple JSON
486
+ * to the same stream. Just continue calling this method, passing it the same IO object with new/different
487
+ * ruby objects to encode. This is how streaming is accomplished.
488
+ */
489
+ static VALUE rb_yajl_encoder_encode(int argc, VALUE * argv, VALUE self) {
350
490
  yajl_gen encoder;
351
491
  const unsigned char * buffer;
352
492
  unsigned int len;
353
- VALUE outBuff;
493
+ VALUE obj, io, blk, outBuff;
354
494
 
355
495
  GetEncoder(self, encoder);
356
496
 
497
+ rb_scan_args(argc, argv, "11&", &obj, &io, &blk);
498
+
357
499
  // begin encode process
358
500
  yajl_encode_part(encoder, obj, io);
359
501
 
360
502
  // just make sure we output the remaining buffer
361
503
  yajl_gen_get_buf(encoder, &buffer, &len);
362
504
  outBuff = rb_str_new((const char *)buffer, len);
363
- rb_io_write(io, outBuff);
364
505
  yajl_gen_clear(encoder);
365
-
506
+ if (io != Qnil) {
507
+ rb_io_write(io, outBuff);
508
+ return Qnil;
509
+ } else if (blk != Qnil) {
510
+ rb_funcall(blk, intern_call, 1, outBuff);
511
+ return Qnil;
512
+ } else {
513
+ return outBuff;
514
+ }
366
515
  return Qnil;
367
516
  }
368
517
 
@@ -372,6 +521,7 @@ void Init_yajl_ext() {
372
521
 
373
522
  VALUE rb_cStandardError = rb_const_get(rb_cObject, rb_intern("StandardError"));
374
523
  cParseError = rb_define_class_under(mYajl, "ParseError", rb_cStandardError);
524
+ cEncodeError = rb_define_class_under(mYajl, "EncodeError", rb_cStandardError);
375
525
 
376
526
  cParser = rb_define_class_under(mYajl, "Parser", rb_cObject);
377
527
  rb_define_singleton_method(cParser, "new", rb_yajl_parser_new, -1);
@@ -384,7 +534,7 @@ void Init_yajl_ext() {
384
534
  cEncoder = rb_define_class_under(mYajl, "Encoder", rb_cObject);
385
535
  rb_define_singleton_method(cEncoder, "new", rb_yajl_encoder_new, -1);
386
536
  rb_define_method(cEncoder, "initialize", rb_yajl_encoder_init, -1);
387
- rb_define_method(cEncoder, "encode", rb_yajl_encoder_encode, 2);
537
+ rb_define_method(cEncoder, "encode", rb_yajl_encoder_encode, -1);
388
538
 
389
539
  intern_io_read = rb_intern("read");
390
540
  intern_eof = rb_intern("eof?");
@@ -395,4 +545,5 @@ void Init_yajl_ext() {
395
545
  sym_check_utf8 = rb_intern("check_utf8");
396
546
  sym_pretty = rb_intern("pretty");
397
547
  sym_indent = rb_intern("indent");
548
+ sym_symbolize_keys = rb_intern("symbolize_keys");
398
549
  }
data/ext/yajl_ext.h CHANGED
@@ -5,9 +5,9 @@
5
5
  #define READ_BUFSIZE 8092
6
6
  #define WRITE_BUFSIZE 8092
7
7
 
8
- static VALUE cParseError, mYajl, cParser, cEncoder;
8
+ static VALUE cParseError, cEncodeError, mYajl, cParser, cEncoder;
9
9
  static ID intern_io_read, intern_eof, intern_call, intern_keys, intern_to_s,
10
- sym_allow_comments, sym_check_utf8, sym_pretty, sym_indent;
10
+ sym_allow_comments, sym_check_utf8, sym_pretty, sym_indent, sym_symbolize_keys;
11
11
 
12
12
  #define GetParser(obj, sval) (sval = (struct yajl_parser_wrapper*)DATA_PTR(obj));
13
13
  #define GetEncoder(obj, sval) (sval = (yajl_gen*)DATA_PTR(obj));
@@ -15,6 +15,7 @@ static ID intern_io_read, intern_eof, intern_call, intern_keys, intern_to_s,
15
15
  inline void yajl_check_and_fire_callback(void * ctx);
16
16
  inline void yajl_set_static_value(void * ctx, VALUE val);
17
17
  void yajl_encode_part(yajl_gen hand, VALUE obj, VALUE io);
18
+ void yajl_parse_chunk(const unsigned char * chunk, unsigned int len, yajl_handle parser);
18
19
 
19
20
  static int yajl_found_null(void * ctx);
20
21
  static int yajl_found_boolean(void * ctx, int boolean);
@@ -44,6 +45,8 @@ struct yajl_parser_wrapper {
44
45
  VALUE parse_complete_callback;
45
46
  int nestedArrayLevel;
46
47
  int nestedHashLevel;
48
+ int objectsFound;
49
+ int symbolizeKeys;
47
50
  yajl_handle parser;
48
51
  };
49
52
  static void yajl_parser_wrapper_free(void * wrapper);
@@ -57,4 +60,4 @@ static VALUE rb_yajl_set_complete_cb(VALUE self, VALUE callback);
57
60
 
58
61
  static VALUE rb_yajl_encoder_new(int argc, VALUE * argv, VALUE klass);
59
62
  static VALUE rb_yajl_encoder_init(int argc, VALUE * argv, VALUE self);
60
- static VALUE rb_yajl_encoder_encode(VALUE self, VALUE obj, VALUE io);
63
+ static VALUE rb_yajl_encoder_encode(int argc, VALUE * argv, VALUE self);
@@ -1,11 +1,11 @@
1
1
  # encoding: UTF-8
2
2
  module Yajl
3
3
  module Bzip2
4
- # === Yajl::Bzip2::StreamReader
5
- #
6
4
  # This is a wrapper around Bzip::Reader to allow it's #read method to adhere
7
5
  # to the IO spec, allowing for two parameters (length, and buffer)
8
6
  class StreamReader < ::Bzip2::Reader
7
+
8
+ # A helper method to allow use similar to IO#read
9
9
  def read(len=nil, buffer=nil)
10
10
  unless buffer.nil?
11
11
  buffer.replace super(len)
@@ -14,8 +14,15 @@ module Yajl
14
14
  super(len)
15
15
  end
16
16
 
17
- def self.parse(io)
18
- Yajl::Parser.new.parse(new(io))
17
+ # Helper method for one-off parsing from a bzip2-compressed stream
18
+ #
19
+ # See Yajl::Parser#parse for parameter documentation
20
+ def self.parse(input, options={}, buffer_size=nil, &block)
21
+ if input.is_a?(String)
22
+ input = StringIO.new(input)
23
+ end
24
+
25
+ Yajl::Parser.new(options).parse(new(input), buffer_size, &block)
19
26
  end
20
27
  end
21
28
  end
@@ -1,8 +1,12 @@
1
1
  # encoding: UTF-8
2
2
  module Yajl
3
3
  module Bzip2
4
- # === Yajl::Bzip2::StreamWriter
4
+ # A wrapper around the Bzip2::Writer class for easier JSON stream encoding
5
5
  class StreamWriter < ::Bzip2::Writer
6
+
7
+ # A helper method for encoding to a bzip2-compressed stream
8
+ #
9
+ # Look up Yajl::Encoder#encode for parameter documentation
6
10
  def self.encode(obj, io)
7
11
  Yajl::Encoder.new.encode(obj, new(io))
8
12
  end
@@ -1,25 +1,36 @@
1
1
  # encoding: UTF-8
2
2
  module Yajl
3
3
  module Deflate
4
- # === Yajl::Deflate::StreamReader
5
- #
6
4
  # This is a wrapper around Zlib::Inflate, creating a #read method that adheres
7
5
  # to the IO spec, allowing for two parameters (length, and buffer)
8
6
  class StreamReader < ::Zlib::Inflate
7
+
8
+ # Wrapper to the initialize method so we can set the initial IO to parse from.
9
9
  def initialize(io, options)
10
10
  @io = io
11
11
  super(options)
12
12
  end
13
13
 
14
+ # A helper method to allow use similar to IO#read
14
15
  def read(len=nil, buffer=nil)
15
16
  buffer.replace inflate(@io.read(len)) and return unless buffer.nil?
16
17
  inflate(@io.read(len))
17
18
  end
18
-
19
19
  alias :eof? :finished?
20
20
 
21
- def self.parse(io, options=nil)
22
- Yajl::Parser.new.parse(new(io, options))
21
+ # Helper method for one-off parsing from a deflate-compressed stream
22
+ #
23
+ # See Yajl::Parser#parse for parameter documentation
24
+ def self.parse(input, options={}, buffer_size=nil, &block)
25
+ if input.is_a?(String)
26
+ input = StringIO.new(input)
27
+ end
28
+
29
+ if options.is_a?(Hash)
30
+ Yajl::Parser.new(options).parse(new(input), buffer_size, &block)
31
+ elsif options.is_a?(Fixnum)
32
+ Yajl::Parser.new.parse(new(input, options), buffer_size, &block)
33
+ end
23
34
  end
24
35
  end
25
36
  end
@@ -1,13 +1,18 @@
1
1
  # encoding: UTF-8
2
2
  module Yajl
3
3
  module Deflate
4
- # === Yajl::Deflate::StreamWriter
4
+ # A wrapper around the Zlib::Deflate class for easier JSON stream parsing
5
5
  class StreamWriter < ::Zlib::Deflate
6
+
7
+ # A helper method to allow use similar to IO#write
6
8
  def write(str)
7
9
  deflate(str)
8
10
  str.size unless str.nil?
9
11
  end
10
12
 
13
+ # A helper method for one-off encoding to a deflate-compressed stream
14
+ #
15
+ # Look up Yajl::Encoder#encode for parameter documentation
11
16
  def self.encode(obj, io)
12
17
  Yajl::Encoder.new.encode(obj, new(io))
13
18
  end
@@ -1,11 +1,11 @@
1
1
  # encoding: UTF-8
2
2
  module Yajl
3
3
  module Gzip
4
- # === Yajl::GzipStreamReader
5
- #
6
4
  # This is a wrapper around Zlib::GzipReader to allow it's #read method to adhere
7
5
  # to the IO spec, allowing for two parameters (length, and buffer)
8
6
  class StreamReader < ::Zlib::GzipReader
7
+
8
+ # Wrapper method to allow use similar to IO#read
9
9
  def read(len=nil, buffer=nil)
10
10
  unless buffer.nil?
11
11
  buffer.replace super(len)
@@ -14,8 +14,14 @@ module Yajl
14
14
  super(len)
15
15
  end
16
16
 
17
- def self.parse(io)
18
- Yajl::Parser.new.parse(new(io))
17
+ # Helper method for one-off parsing from a gzip-compressed stream
18
+ #
19
+ # See Yajl::Parser#parse for parameter documentation
20
+ def self.parse(input, options={}, buffer_size=nil, &block)
21
+ if input.is_a?(String)
22
+ input = StringIO.new(input)
23
+ end
24
+ Yajl::Parser.new(options).parse(new(input), buffer_size, &block)
19
25
  end
20
26
  end
21
27
  end
@@ -1,8 +1,11 @@
1
1
  # encoding: UTF-8
2
2
  module Yajl
3
3
  module Gzip
4
- # === Yajl::Gzip::StreamWriter
4
+ # Wraper around the Zlib::GzipWriter class
5
5
  class StreamWriter < ::Zlib::GzipWriter
6
+ # A helper method for one-off encoding to a gzip-compressed stream
7
+ #
8
+ # Look up Yajl::Encoder#encode for parameter documentation
6
9
  def self.encode(obj, io)
7
10
  Yajl::Encoder.new.encode(obj, new(io))
8
11
  end
@@ -3,13 +3,10 @@ require 'socket' unless defined?(Socket)
3
3
  require 'yajl' unless defined?(Yajl::Parser)
4
4
 
5
5
  module Yajl
6
- # == Yajl::HttpStream
7
- #
8
6
  # This module is for making HTTP requests to which the response bodies (and possibly requests in the near future)
9
7
  # are streamed directly into Yajl.
10
8
  class HttpStream
11
- # === Yajl::HttpStream::InvalidContentType
12
- #
9
+
13
10
  # This Exception is thrown when an HTTP response isn't application/json
14
11
  # and therefore cannot be parsed.
15
12
  class InvalidContentType < Exception; end
@@ -22,8 +19,7 @@ module Yajl
22
19
  # 1. a raw socket is opened to the server/host provided
23
20
  # 2. the request is made using HTTP/1.0, Accept-encoding: gzip (deflate support coming soon, too)
24
21
  # 3. the response is read until the end of the headers
25
- # 4. the _socket itself_ is passed directly to Yajl, for direct parsing off the stream;
26
- # As it's being received over the wire!
22
+ # 4. the _socket itself_ is passed directly to Yajl, for direct parsing off the stream; As it's being received over the wire!
27
23
  def self.get(uri, opts = {}, &block)
28
24
  user_agent = opts.has_key?(['User-Agent']) ? opts['User-Agent'] : "Yajl::HttpStream #{Yajl::VERSION}"
29
25
 
data/lib/yajl.rb CHANGED
@@ -13,39 +13,65 @@ require 'yajl_ext'
13
13
  #
14
14
  # Ruby bindings to the excellent Yajl (Yet Another JSON Parser) ANSI C library.
15
15
  module Yajl
16
- VERSION = "0.5.2"
16
+ VERSION = "0.5.3"
17
17
 
18
- # == Yajl::Parser
19
- #
20
- # This class contains methods for parsing JSON directly from an IO object.
21
- # The only basic requirment currently is that the IO object respond to #read(len) and #eof?
22
- # The IO is parsed until a complete JSON object has been read and a ruby object will be returned.
23
18
  class Parser
24
- def self.parse(io, options={})
25
- new(options).parse(io)
19
+ # A helper method for parse-and-forget use-cases
20
+ #
21
+ # +io+ is the stream to parse JSON from
22
+ #
23
+ # The +options+ hash allows you to set two parsing options - :allow_comments and :check_utf8
24
+ #
25
+ # :allow_comments accepts a boolean will enable/disable checks for in-line comments in the JSON stream
26
+ #
27
+ # :check_utf8 accepts a boolean will enable/disable UTF8 validation for the JSON stream
28
+ def self.parse(io, options={}, read_bufsize=nil, &block)
29
+ new(options).parse(io, read_bufsize, &block)
26
30
  end
27
31
  end
28
32
 
29
- # == Yajl::Encoder
30
- #
31
- # This class contains methods for encoding a Ruby object into JSON, streaming it's output into an IO object.
32
- # The IO object need only respond to #write(str)
33
- # The JSON stream created is written to the IO in chunks, as it's being created.
34
33
  class Encoder
35
- def self.encode(obj, io, options={})
36
- new(options).encode(obj, io)
34
+ # A helper method for encode-and-forget use-cases
35
+ #
36
+ # Examples:
37
+ # Yajl::Encoder.encode(obj[, io, :pretty => true, :indent => "\t"])
38
+ #
39
+ # output = Yajl::Encoder.encode(obj[, :pretty => true, :indent => "\t"])
40
+ #
41
+ # +obj+ is a ruby object to encode to JSON format
42
+ #
43
+ # +io+ is the optional IO stream to encode the ruby object to.
44
+ # If +io+ isn't passed, the resulting JSON string is returned. If +io+ is passed, nil is returned.
45
+ #
46
+ # The +options+ hash allows you to set two encoding options - :pretty and :indent
47
+ #
48
+ # :pretty accepts a boolean and will enable/disable "pretty printing" the resulting output
49
+ #
50
+ # :indent accepts a string and will be used as the indent character(s) during the pretty print process
51
+ def self.encode(obj, *args, &block)
52
+ # TODO: this code smells, any ideas?
53
+ options = {}
54
+ io = nil
55
+ args.each do |arg|
56
+ if arg.is_a?(Hash)
57
+ options = arg
58
+ elsif arg.respond_to?(:read)
59
+ io = arg
60
+ end
61
+ end if args.any?
62
+ new(options).encode(obj, io, &block)
37
63
  end
38
64
  end
39
65
 
40
- # Deprecated - See Yajl::Parser and Yajl::Encoder
66
+ # DEPRECATED - See Yajl::Parser and Yajl::Encoder
41
67
  module Stream
42
- # Deprecated - See Yajl::Parser
68
+ # DEPRECATED - See Yajl::Parser
43
69
  def self.parse(io)
44
70
  STDERR.puts "WARNING: Yajl::Stream has be deprecated and will most likely be gone in the next release. Use the Yajl::Parser class instead."
45
71
  Parser.new.parse(io)
46
72
  end
47
73
 
48
- # Deprecated - See Yajl::Encoder
74
+ # DEPRECATED - See Yajl::Encoder
49
75
  def self.encode(obj, io)
50
76
  STDERR.puts "WARNING: Yajl::Stream has be deprecated and will most likely be gone in the next release. Use the Yajl::Encoder class instead."
51
77
  Encoder.new.encode(obj, io)
@@ -5,28 +5,57 @@ describe "Yajl JSON encoder" do
5
5
  FILES = Dir[File.dirname(__FILE__)+'/../../benchmark/subjects/*.json']
6
6
 
7
7
  FILES.each do |file|
8
- it "should encode #{File.basename(file)}" do
8
+ it "should encode #{File.basename(file)} to an IO" do
9
9
  # we don't care about testing the stream subject as it has multiple JSON strings in it
10
10
  if File.basename(file) != 'twitter_stream.json'
11
11
  input = File.new(File.expand_path(file), 'r')
12
12
  io = StringIO.new
13
- parser = Yajl::Parser.new
14
13
  encoder = Yajl::Encoder.new
15
-
16
- hash = parser.parse(input)
17
- output = encoder.encode(hash, io)
14
+ hash = Yajl::Parser.parse(input)
15
+ encoder.encode(hash, io)
18
16
  io.rewind
19
- hash2 = parser.parse(io)
20
-
17
+ hash2 = Yajl::Parser.parse(io)
21
18
  io.close
22
19
  input.close
23
-
24
20
  hash.should == hash2
25
21
  end
26
22
  end
27
23
  end
28
24
 
29
- it "should encode with :pretty turned on and a single space indent" do
25
+ FILES.each do |file|
26
+ it "should encode #{File.basename(file)} and return a String" do
27
+ # we don't care about testing the stream subject as it has multiple JSON strings in it
28
+ if File.basename(file) != 'twitter_stream.json'
29
+ input = File.new(File.expand_path(file), 'r')
30
+ encoder = Yajl::Encoder.new
31
+ hash = Yajl::Parser.parse(input)
32
+ output = encoder.encode(hash)
33
+ hash2 = Yajl::Parser.parse(output)
34
+ input.close
35
+ hash.should == hash2
36
+ end
37
+ end
38
+ end
39
+
40
+ FILES.each do |file|
41
+ it "should encode #{File.basename(file)} call the passed block, passing it a String" do
42
+ # we don't care about testing the stream subject as it has multiple JSON strings in it
43
+ if File.basename(file) != 'twitter_stream.json'
44
+ input = File.new(File.expand_path(file), 'r')
45
+ encoder = Yajl::Encoder.new
46
+ hash = Yajl::Parser.parse(input)
47
+ output = ''
48
+ encoder.encode(hash) do |json_str|
49
+ output = json_str
50
+ end
51
+ hash2 = Yajl::Parser.parse(output)
52
+ input.close
53
+ hash.should == hash2
54
+ end
55
+ end
56
+ end
57
+
58
+ it "should encode with :pretty turned on and a single space indent, to an IO" do
30
59
  output = "{\n \"foo\": {\n \"name\": \"bar\",\n \"id\": 1234\n }\n}\n"
31
60
  if RUBY_VERSION.include?('1.9') # FIXME
32
61
  output = "{\n \"foo\": {\n \"id\": 1234,\n \"name\": \"bar\"\n }\n}\n"
@@ -39,7 +68,18 @@ describe "Yajl JSON encoder" do
39
68
  io.read.should == output
40
69
  end
41
70
 
42
- it "should encode with :pretty turned on and a tab character indent" do
71
+ it "should encode with :pretty turned on and a single space indent, and return a String" do
72
+ output = "{\n \"foo\": {\n \"name\": \"bar\",\n \"id\": 1234\n }\n}\n"
73
+ if RUBY_VERSION.include?('1.9') # FIXME
74
+ output = "{\n \"foo\": {\n \"id\": 1234,\n \"name\": \"bar\"\n }\n}\n"
75
+ end
76
+ obj = {:foo => {:id => 1234, :name => "bar"}}
77
+ encoder = Yajl::Encoder.new(:pretty => true, :indent => ' ')
78
+ output = encoder.encode(obj)
79
+ output.should == output
80
+ end
81
+
82
+ it "should encode with :pretty turned on and a tab character indent, to an IO" do
43
83
  output = "{\n\t\"foo\": {\n\t\t\"name\": \"bar\",\n\t\t\"id\": 1234\n\t}\n}\n"
44
84
  if RUBY_VERSION.include?('1.9') # FIXME
45
85
  output = "{\n\t\"foo\": {\n\t\t\"id\": 1234,\n\t\t\"name\": \"bar\"\n\t}\n}\n"
@@ -52,7 +92,18 @@ describe "Yajl JSON encoder" do
52
92
  io.read.should == output
53
93
  end
54
94
 
55
- it "should encode with it's class method with :pretty and a tab character indent options set" do
95
+ it "should encode with :pretty turned on and a tab character indent, and return a String" do
96
+ output = "{\n\t\"foo\": {\n\t\t\"name\": \"bar\",\n\t\t\"id\": 1234\n\t}\n}\n"
97
+ if RUBY_VERSION.include?('1.9') # FIXME
98
+ output = "{\n\t\"foo\": {\n\t\t\"id\": 1234,\n\t\t\"name\": \"bar\"\n\t}\n}\n"
99
+ end
100
+ obj = {:foo => {:id => 1234, :name => "bar"}}
101
+ encoder = Yajl::Encoder.new(:pretty => true, :indent => "\t")
102
+ output = encoder.encode(obj)
103
+ output.should == output
104
+ end
105
+
106
+ it "should encode with it's class method with :pretty and a tab character indent options set, to an IO" do
56
107
  output = "{\n\t\"foo\": {\n\t\t\"name\": \"bar\",\n\t\t\"id\": 1234\n\t}\n}\n"
57
108
  if RUBY_VERSION.include?('1.9') # FIXME
58
109
  output = "{\n\t\"foo\": {\n\t\t\"id\": 1234,\n\t\t\"name\": \"bar\"\n\t}\n}\n"
@@ -64,7 +115,31 @@ describe "Yajl JSON encoder" do
64
115
  io.read.should == output
65
116
  end
66
117
 
67
- it "should encode multiple objects into a single stream" do
118
+ it "should encode with it's class method with :pretty and a tab character indent options set, and return a String" do
119
+ output = "{\n\t\"foo\": {\n\t\t\"name\": \"bar\",\n\t\t\"id\": 1234\n\t}\n}\n"
120
+ if RUBY_VERSION.include?('1.9') # FIXME
121
+ output = "{\n\t\"foo\": {\n\t\t\"id\": 1234,\n\t\t\"name\": \"bar\"\n\t}\n}\n"
122
+ end
123
+ obj = {:foo => {:id => 1234, :name => "bar"}}
124
+ output = Yajl::Encoder.encode(obj, :pretty => true, :indent => "\t")
125
+ output.should == output
126
+ end
127
+
128
+ it "should encode with it's class method with :pretty and a tab character indent options set, to a block" do
129
+ output = "{\n\t\"foo\": {\n\t\t\"name\": \"bar\",\n\t\t\"id\": 1234\n\t}\n}\n"
130
+ if RUBY_VERSION.include?('1.9') # FIXME
131
+ output = "{\n\t\"foo\": {\n\t\t\"id\": 1234,\n\t\t\"name\": \"bar\"\n\t}\n}\n"
132
+ end
133
+ obj = {:foo => {:id => 1234, :name => "bar"}}
134
+ output = ''
135
+ Yajl::Encoder.encode(obj, :pretty => true, :indent => "\t") do |json_str|
136
+ output = json_str
137
+ end
138
+ output.should == output
139
+ end
140
+
141
+ it "should encode multiple objects into a single stream, to an IO" do
142
+ pending "Find a better way to compare order of hash keys in resulting string"
68
143
  io = StringIO.new
69
144
  obj = {:foo => "bar", :baz => 1234}
70
145
  encoder = Yajl::Encoder.new
@@ -72,6 +147,25 @@ describe "Yajl JSON encoder" do
72
147
  encoder.encode(obj, io)
73
148
  end
74
149
  io.rewind
75
- io.read.should == "{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n"
150
+ output = "{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n"
151
+ if RUBY_VERSION.include?('1.9') # FIXME
152
+ output = "{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n"
153
+ end
154
+ io.read.should == output
155
+ end
156
+
157
+ it "should encode multiple objects into a single stream, and return a String" do
158
+ pending "Find a better way to compare order of hash keys in resulting string"
159
+ obj = {:foo => "bar", :baz => 1234}
160
+ encoder = Yajl::Encoder.new
161
+ json_output = ''
162
+ 5.times do
163
+ json_output << encoder.encode(obj)
164
+ end
165
+ output = "{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n{\"baz\":1234,\"foo\":\"bar\"}\n"
166
+ if RUBY_VERSION.include?('1.9') # FIXME
167
+ output = "{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n{\"foo\":\"bar\",\"baz\":1234}\n"
168
+ end
169
+ json_output.should == output
76
170
  end
77
171
  end
@@ -35,18 +35,34 @@ describe "ActiveSupport test cases" do
35
35
  }
36
36
 
37
37
  TESTS.each do |json, expected|
38
- it "should be able to parse #{json}" do
38
+ it "should be able to parse #{json} as an IO" do
39
39
  lambda {
40
40
  parser = Yajl::Parser.new
41
41
  parser.parse(StringIO.new(json)).should == expected
42
42
  }.should_not raise_error(Yajl::ParseError)
43
43
  end
44
44
  end
45
+
46
+ TESTS.each do |json, expected|
47
+ it "should be able to parse #{json} as a string" do
48
+ lambda {
49
+ parser = Yajl::Parser.new
50
+ parser.parse(json).should == expected
51
+ }.should_not raise_error(Yajl::ParseError)
52
+ end
53
+ end
45
54
 
46
- it "should fail parsing {: 1}" do
55
+ it "should fail parsing {: 1} as an IO" do
47
56
  lambda {
48
57
  parser = Yajl::Parser.new
49
58
  parser.parse(StringIO.new("{: 1}"))
50
59
  }.should raise_error(Yajl::ParseError)
51
60
  end
61
+
62
+ it "should fail parsing {: 1} as a string" do
63
+ lambda {
64
+ parser = Yajl::Parser.new
65
+ parser.parse("{: 1}")
66
+ }.should raise_error(Yajl::ParseError)
67
+ end
52
68
  end
@@ -4,7 +4,7 @@ require 'stringio'
4
4
 
5
5
  describe "Chunked parser" do
6
6
  before(:all) do
7
- @final = [{"abc" => 123}, {"def" => 456}]
7
+ @final = [{:abc => 123}, {:def => 456}]
8
8
  end
9
9
 
10
10
  before(:each) do
@@ -67,6 +67,32 @@ describe "Chunked parser" do
67
67
  path = File.expand_path(File.dirname(__FILE__) + '/../../benchmark/subjects/twitter_stream.json')
68
68
  json = File.new(path, 'r')
69
69
  @callback.should_receive(:call).exactly(430).times
70
- @parser.parse(json)
70
+ lambda {
71
+ @parser.parse(json)
72
+ }.should_not raise_error(Yajl::ParseError)
73
+ end
74
+
75
+ it "should parse twitter_stream.json and fire callback 430 times, with a block as the callback" do
76
+ path = File.expand_path(File.dirname(__FILE__) + '/../../benchmark/subjects/twitter_stream.json')
77
+ json = File.new(path, 'r')
78
+ @callback.should_receive(:call).exactly(0).times
79
+ @parser.on_parse_complete = nil
80
+ lambda {
81
+ times = 0
82
+ @parser.parse(json) do |hsh|
83
+ times += 1
84
+ end
85
+ times.should eql(430)
86
+ }.should_not raise_error(Yajl::ParseError)
87
+ end
88
+
89
+ it "should raise a Yajl::ParseError error if multiple JSON strings were found when no on_parse_complete callback assigned" do
90
+ path = File.expand_path(File.dirname(__FILE__) + '/../../benchmark/subjects/twitter_stream.json')
91
+ json = File.new(path, 'r')
92
+ @parser.on_parse_complete = nil
93
+ @callback.should_receive(:call).exactly(0).times
94
+ lambda {
95
+ @parser.parse(json)
96
+ }.should raise_error(Yajl::ParseError)
71
97
  end
72
98
  end
@@ -8,7 +8,7 @@ describe "Parsing JSON Fixtures" do
8
8
  FAILED = failed.inject([]) { |a, f| a << [ f, File.read(f) ] }.sort
9
9
 
10
10
  FAILED.each do |name, source|
11
- it "should not be able to parse #{File.basename(name)}" do
11
+ it "should not be able to parse #{File.basename(name)} as an IO" do
12
12
  lambda {
13
13
  parser = Yajl::Parser.new
14
14
  parser.parse(StringIO.new(source))
@@ -16,12 +16,30 @@ describe "Parsing JSON Fixtures" do
16
16
  end
17
17
  end
18
18
 
19
+ FAILED.each do |name, source|
20
+ it "should not be able to parse #{File.basename(name)} as a string" do
21
+ lambda {
22
+ parser = Yajl::Parser.new
23
+ parser.parse(source)
24
+ }.should raise_error(Yajl::ParseError)
25
+ end
26
+ end
27
+
19
28
  PASSED.each do |name, source|
20
- it "should be able to parse #{File.basename(name)}" do
29
+ it "should be able to parse #{File.basename(name)} as an IO" do
21
30
  lambda {
22
31
  parser = Yajl::Parser.new
23
32
  parser.parse(StringIO.new(source))
24
33
  }.should_not raise_error(Yajl::ParseError)
25
34
  end
26
35
  end
36
+
37
+ PASSED.each do |name, source|
38
+ it "should be able to parse #{File.basename(name)} as a string" do
39
+ lambda {
40
+ parser = Yajl::Parser.new
41
+ parser.parse(source)
42
+ }.should_not raise_error(Yajl::ParseError)
43
+ end
44
+ end
27
45
  end
@@ -6,7 +6,7 @@ describe "One-off JSON examples" do
6
6
  infinity = (1.0/0)
7
7
  silence_warnings do
8
8
  parser = Yajl::Parser.new
9
- parser.parse(StringIO.new('{"key": 23456789012E666}')).should == {"key" => infinity}
9
+ parser.parse(StringIO.new('{"key": 23456789012E666}')).should == {:key => infinity}
10
10
  end
11
11
  end
12
12
 
@@ -27,17 +27,32 @@ describe "One-off JSON examples" do
27
27
  end
28
28
 
29
29
  it "should not parse invalid UTF8 with :check_utf8 set to true" do
30
- pending
31
- # not sure how to write this test yet
30
+ pending "not sure how to write this test yet"
32
31
  end
33
32
 
34
33
  it "should parse invalid UTF8 with :check_utf8 set to false" do
35
- pending
36
- # not sure how to write this test yet
34
+ pending "not sure how to write this test yet"
37
35
  end
38
36
 
39
- it "should parse using it's class method" do
37
+ it "should parse using it's class method, from an IO" do
40
38
  io = StringIO.new('{"key": 1234}')
41
- Yajl::Parser.parse(io).should == {"key" => 1234}
39
+ Yajl::Parser.parse(io).should == {:key => 1234}
40
+ end
41
+
42
+ it "should parse using it's class method, from an IO with string keys" do
43
+ parser = Yajl::Parser.new(:symbolize_keys => true)
44
+ parser.parse('{"key": 1234}').should == {:key => 1234}
45
+ end
46
+
47
+ it "should parse using it's class method, from a string" do
48
+ Yajl::Parser.parse('{"key": 1234}').should == {:key => 1234}
49
+ end
50
+
51
+ it "should parse using it's class method, from a string with a block" do
52
+ output = nil
53
+ Yajl::Parser.parse('{"key": 1234}') do |obj|
54
+ output = obj
55
+ end
56
+ output.should == {:key => 1234}
42
57
  end
43
58
  end
data/yajl-ruby.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{yajl-ruby}
5
- s.version = "0.5.2"
5
+ s.version = "0.5.3"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Brian Lopez", "Lloyd Hilaiel"]
9
- s.date = %q{2009-05-30}
9
+ s.date = %q{2009-06-07}
10
10
  s.email = %q{seniorlopez@gmail.com}
11
11
  s.extensions = ["ext/extconf.rb"]
12
12
  s.extra_rdoc_files = [
@@ -142,7 +142,7 @@ Gem::Specification.new do |s|
142
142
  s.homepage = %q{http://github.com/brianmario/yajl-ruby}
143
143
  s.rdoc_options = ["--charset=UTF-8"]
144
144
  s.require_paths = ["lib", "ext"]
145
- s.rubygems_version = %q{1.3.3}
145
+ s.rubygems_version = %q{1.3.4}
146
146
  s.summary = %q{Ruby C bindings to the excellent Yajl JSON stream-based parser library.}
147
147
  s.test_files = [
148
148
  "spec/encoding/encoding_spec.rb",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brianmario-yajl-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Lopez
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-05-30 00:00:00 -07:00
13
+ date: 2009-06-07 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies: []
16
16