mortar-api-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,610 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright 2011, 2012 Keith Rarick
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ # See https://github.com/kr/okjson for updates.
24
+
25
+ require 'stringio'
26
+
27
+ # Some parts adapted from
28
+ # http://golang.org/src/pkg/json/decode.go and
29
+ # http://golang.org/src/pkg/utf8/utf8.go
30
+ module Mortar
31
+ class API
32
+ module OkJson
33
+ extend self
34
+
35
+
36
+ # Decodes a json document in string s and
37
+ # returns the corresponding ruby value.
38
+ # String s must be valid UTF-8. If you have
39
+ # a string in some other encoding, convert
40
+ # it first.
41
+ #
42
+ # String values in the resulting structure
43
+ # will be UTF-8.
44
+ def decode(s)
45
+ ts = lex(s)
46
+ v, ts = textparse(ts)
47
+ if ts.length > 0
48
+ raise Error, 'trailing garbage'
49
+ end
50
+ v
51
+ end
52
+
53
+
54
+ # Parses a "json text" in the sense of RFC 4627.
55
+ # Returns the parsed value and any trailing tokens.
56
+ # Note: this is almost the same as valparse,
57
+ # except that it does not accept atomic values.
58
+ def textparse(ts)
59
+ if ts.length < 0
60
+ raise Error, 'empty'
61
+ end
62
+
63
+ typ, _, val = ts[0]
64
+ case typ
65
+ when '{' then objparse(ts)
66
+ when '[' then arrparse(ts)
67
+ else
68
+ raise Error, "unexpected #{val.inspect}"
69
+ end
70
+ end
71
+
72
+
73
+ # Parses a "value" in the sense of RFC 4627.
74
+ # Returns the parsed value and any trailing tokens.
75
+ def valparse(ts)
76
+ if ts.length < 0
77
+ raise Error, 'empty'
78
+ end
79
+
80
+ typ, _, val = ts[0]
81
+ case typ
82
+ when '{' then objparse(ts)
83
+ when '[' then arrparse(ts)
84
+ when :val,:str then [val, ts[1..-1]]
85
+ else
86
+ raise Error, "unexpected #{val.inspect}"
87
+ end
88
+ end
89
+
90
+
91
+ # Parses an "object" in the sense of RFC 4627.
92
+ # Returns the parsed value and any trailing tokens.
93
+ def objparse(ts)
94
+ ts = eat('{', ts)
95
+ obj = {}
96
+
97
+ if ts[0][0] == '}'
98
+ return obj, ts[1..-1]
99
+ end
100
+
101
+ k, v, ts = pairparse(ts)
102
+ obj[k] = v
103
+
104
+ if ts[0][0] == '}'
105
+ return obj, ts[1..-1]
106
+ end
107
+
108
+ loop do
109
+ ts = eat(',', ts)
110
+
111
+ k, v, ts = pairparse(ts)
112
+ obj[k] = v
113
+
114
+ if ts[0][0] == '}'
115
+ return obj, ts[1..-1]
116
+ end
117
+ end
118
+ end
119
+
120
+
121
+ # Parses a "member" in the sense of RFC 4627.
122
+ # Returns the parsed values and any trailing tokens.
123
+ def pairparse(ts)
124
+ (typ, _, k), ts = ts[0], ts[1..-1]
125
+ if typ != :str
126
+ raise Error, "unexpected #{k.inspect}"
127
+ end
128
+ ts = eat(':', ts)
129
+ v, ts = valparse(ts)
130
+ [k, v, ts]
131
+ end
132
+
133
+
134
+ # Parses an "array" in the sense of RFC 4627.
135
+ # Returns the parsed value and any trailing tokens.
136
+ def arrparse(ts)
137
+ ts = eat('[', ts)
138
+ arr = []
139
+
140
+ if ts[0][0] == ']'
141
+ return arr, ts[1..-1]
142
+ end
143
+
144
+ v, ts = valparse(ts)
145
+ arr << v
146
+
147
+ if ts[0][0] == ']'
148
+ return arr, ts[1..-1]
149
+ end
150
+
151
+ loop do
152
+ ts = eat(',', ts)
153
+
154
+ v, ts = valparse(ts)
155
+ arr << v
156
+
157
+ if ts[0][0] == ']'
158
+ return arr, ts[1..-1]
159
+ end
160
+ end
161
+ end
162
+
163
+
164
+ def eat(typ, ts)
165
+ if ts[0][0] != typ
166
+ raise Error, "expected #{typ} (got #{ts[0].inspect})"
167
+ end
168
+ ts[1..-1]
169
+ end
170
+
171
+
172
+ # Scans s and returns a list of json tokens,
173
+ # excluding white space (as defined in RFC 4627).
174
+ def lex(s)
175
+ ts = []
176
+ while s.length > 0
177
+ typ, lexeme, val = tok(s)
178
+ if typ == nil
179
+ raise Error, "invalid character at #{s[0,10].inspect}"
180
+ end
181
+ if typ != :space
182
+ ts << [typ, lexeme, val]
183
+ end
184
+ s = s[lexeme.length..-1]
185
+ end
186
+ ts
187
+ end
188
+
189
+
190
+ # Scans the first token in s and
191
+ # returns a 3-element list, or nil
192
+ # if s does not begin with a valid token.
193
+ #
194
+ # The first list element is one of
195
+ # '{', '}', ':', ',', '[', ']',
196
+ # :val, :str, and :space.
197
+ #
198
+ # The second element is the lexeme.
199
+ #
200
+ # The third element is the value of the
201
+ # token for :val and :str, otherwise
202
+ # it is the lexeme.
203
+ def tok(s)
204
+ case s[0]
205
+ when ?{ then ['{', s[0,1], s[0,1]]
206
+ when ?} then ['}', s[0,1], s[0,1]]
207
+ when ?: then [':', s[0,1], s[0,1]]
208
+ when ?, then [',', s[0,1], s[0,1]]
209
+ when ?[ then ['[', s[0,1], s[0,1]]
210
+ when ?] then [']', s[0,1], s[0,1]]
211
+ when ?n then nulltok(s)
212
+ when ?t then truetok(s)
213
+ when ?f then falsetok(s)
214
+ when ?" then strtok(s)
215
+ when Spc then [:space, s[0,1], s[0,1]]
216
+ when ?\t then [:space, s[0,1], s[0,1]]
217
+ when ?\n then [:space, s[0,1], s[0,1]]
218
+ when ?\r then [:space, s[0,1], s[0,1]]
219
+ else numtok(s)
220
+ end
221
+ end
222
+
223
+
224
+ def nulltok(s); s[0,4] == 'null' && [:val, 'null', nil] end
225
+ def truetok(s); s[0,4] == 'true' && [:val, 'true', true] end
226
+ def falsetok(s); s[0,5] == 'false' && [:val, 'false', false] end
227
+
228
+
229
+ def numtok(s)
230
+ m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
231
+ if m && m.begin(0) == 0
232
+ if m[3] && !m[2]
233
+ [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
234
+ elsif m[2]
235
+ [:val, m[0], Float(m[0])]
236
+ else
237
+ [:val, m[0], Integer(m[0])]
238
+ end
239
+ end
240
+ end
241
+
242
+
243
+ def strtok(s)
244
+ m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
245
+ if ! m
246
+ raise Error, "invalid string literal at #{abbrev(s)}"
247
+ end
248
+ [:str, m[0], unquote(m[0])]
249
+ end
250
+
251
+
252
+ def abbrev(s)
253
+ t = s[0,10]
254
+ p = t['`']
255
+ t = t[0,p] if p
256
+ t = t + '...' if t.length < s.length
257
+ '`' + t + '`'
258
+ end
259
+
260
+
261
+ # Converts a quoted json string literal q into a UTF-8-encoded string.
262
+ # The rules are different than for Ruby, so we cannot use eval.
263
+ # Unquote will raise an error if q contains control characters.
264
+ def unquote(q)
265
+ q = q[1...-1]
266
+ a = q.dup # allocate a big enough string
267
+ rubydoesenc = false
268
+ # In ruby >= 1.9, a[w] is a codepoint, not a byte.
269
+ if a.class.method_defined?(:force_encoding)
270
+ a.force_encoding('UTF-8')
271
+ rubydoesenc = true
272
+ end
273
+ r, w = 0, 0
274
+ while r < q.length
275
+ c = q[r]
276
+ case true
277
+ when c == ?\\
278
+ r += 1
279
+ if r >= q.length
280
+ raise Error, "string literal ends with a \"\\\": \"#{q}\""
281
+ end
282
+
283
+ case q[r]
284
+ when ?",?\\,?/,?'
285
+ a[w] = q[r]
286
+ r += 1
287
+ w += 1
288
+ when ?b,?f,?n,?r,?t
289
+ a[w] = Unesc[q[r]]
290
+ r += 1
291
+ w += 1
292
+ when ?u
293
+ r += 1
294
+ uchar = begin
295
+ hexdec4(q[r,4])
296
+ rescue RuntimeError => e
297
+ raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
298
+ end
299
+ r += 4
300
+ if surrogate? uchar
301
+ if q.length >= r+6
302
+ uchar1 = hexdec4(q[r+2,4])
303
+ uchar = subst(uchar, uchar1)
304
+ if uchar != Ucharerr
305
+ # A valid pair; consume.
306
+ r += 6
307
+ end
308
+ end
309
+ end
310
+ if rubydoesenc
311
+ a[w] = '' << uchar
312
+ w += 1
313
+ else
314
+ w += ucharenc(a, w, uchar)
315
+ end
316
+ else
317
+ raise Error, "invalid escape char #{q[r]} in \"#{q}\""
318
+ end
319
+ when c == ?", c < Spc
320
+ raise Error, "invalid character in string literal \"#{q}\""
321
+ else
322
+ # Copy anything else byte-for-byte.
323
+ # Valid UTF-8 will remain valid UTF-8.
324
+ # Invalid UTF-8 will remain invalid UTF-8.
325
+ # In ruby >= 1.9, c is a codepoint, not a byte,
326
+ # in which case this is still what we want.
327
+ a[w] = c
328
+ r += 1
329
+ w += 1
330
+ end
331
+ end
332
+ a[0,w]
333
+ end
334
+
335
+
336
+ # Encodes unicode character u as UTF-8
337
+ # bytes in string a at position i.
338
+ # Returns the number of bytes written.
339
+ def ucharenc(a, i, u)
340
+ case true
341
+ when u <= Uchar1max
342
+ a[i] = (u & 0xff).chr
343
+ 1
344
+ when u <= Uchar2max
345
+ a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
346
+ a[i+1] = (Utagx | (u&Umaskx)).chr
347
+ 2
348
+ when u <= Uchar3max
349
+ a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
350
+ a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
351
+ a[i+2] = (Utagx | (u&Umaskx)).chr
352
+ 3
353
+ else
354
+ a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
355
+ a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
356
+ a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
357
+ a[i+3] = (Utagx | (u&Umaskx)).chr
358
+ 4
359
+ end
360
+ end
361
+
362
+
363
+ def hexdec4(s)
364
+ if s.length != 4
365
+ raise Error, 'short'
366
+ end
367
+ (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
368
+ end
369
+
370
+
371
+ def subst(u1, u2)
372
+ if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
373
+ return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
374
+ end
375
+ return Ucharerr
376
+ end
377
+
378
+
379
+ def unsubst(u)
380
+ if u < Usurrself || u > Umax || surrogate?(u)
381
+ return Ucharerr, Ucharerr
382
+ end
383
+ u -= Usurrself
384
+ [Usurr1 + ((u>>10)&0x3ff), Usurr2 + (u&0x3ff)]
385
+ end
386
+
387
+
388
+ def surrogate?(u)
389
+ Usurr1 <= u && u < Usurr3
390
+ end
391
+
392
+
393
+ def nibble(c)
394
+ case true
395
+ when ?0 <= c && c <= ?9 then c.ord - ?0.ord
396
+ when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
397
+ when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
398
+ else
399
+ raise Error, "invalid hex code #{c}"
400
+ end
401
+ end
402
+
403
+
404
+ # Encodes x into a json text. It may contain only
405
+ # Array, Hash, String, Numeric, true, false, nil.
406
+ # (Note, this list excludes Symbol.)
407
+ # X itself must be an Array or a Hash.
408
+ # No other value can be encoded, and an error will
409
+ # be raised if x contains any other value, such as
410
+ # Nan, Infinity, Symbol, and Proc, or if a Hash key
411
+ # is not a String.
412
+ # Strings contained in x must be valid UTF-8.
413
+ def encode(x)
414
+ case x
415
+ when Hash then objenc(x)
416
+ when Array then arrenc(x)
417
+ else
418
+ raise Error, 'root value must be an Array or a Hash'
419
+ end
420
+ end
421
+
422
+
423
+ def valenc(x)
424
+ case x
425
+ when Hash then objenc(x)
426
+ when Array then arrenc(x)
427
+ when String then strenc(x)
428
+ when Numeric then numenc(x)
429
+ when true then "true"
430
+ when false then "false"
431
+ when nil then "null"
432
+ else
433
+ raise Error, "cannot encode #{x.class}: #{x.inspect}"
434
+ end
435
+ end
436
+
437
+
438
+ def objenc(x)
439
+ '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
440
+ end
441
+
442
+
443
+ def arrenc(a)
444
+ '[' + a.map{|x| valenc(x)}.join(',') + ']'
445
+ end
446
+
447
+
448
+ def keyenc(k)
449
+ case k
450
+ when String then strenc(k)
451
+ else
452
+ raise Error, "Hash key is not a string: #{k.inspect}"
453
+ end
454
+ end
455
+
456
+
457
+ def strenc(s)
458
+ t = StringIO.new
459
+ t.putc(?")
460
+ r = 0
461
+
462
+ # In ruby >= 1.9, s[r] is a codepoint, not a byte.
463
+ rubydoesenc = s.class.method_defined?(:encoding)
464
+
465
+ while r < s.length
466
+ case s[r]
467
+ when ?" then t.print('\\"')
468
+ when ?\\ then t.print('\\\\')
469
+ when ?\b then t.print('\\b')
470
+ when ?\f then t.print('\\f')
471
+ when ?\n then t.print('\\n')
472
+ when ?\r then t.print('\\r')
473
+ when ?\t then t.print('\\t')
474
+ else
475
+ c = s[r]
476
+ case true
477
+ when Spc <= c && c <= ?~
478
+ t.putc(c)
479
+ when rubydoesenc
480
+ u = c.ord
481
+ surrenc(t, u)
482
+ else
483
+ u, size = uchardec(s, r)
484
+ r += size - 1 # we add one more at the bottom of the loop
485
+ surrenc(t, u)
486
+ end
487
+ end
488
+ r += 1
489
+ end
490
+ t.putc(?")
491
+ t.string
492
+ end
493
+
494
+
495
+ def surrenc(t, u)
496
+ if u < 0x10000
497
+ t.print('\\u')
498
+ hexenc4(t, u)
499
+ else
500
+ u1, u2 = unsubst(u)
501
+ t.print('\\u')
502
+ hexenc4(t, u1)
503
+ t.print('\\u')
504
+ hexenc4(t, u2)
505
+ end
506
+ end
507
+
508
+
509
+ def hexenc4(t, u)
510
+ t.putc(Hex[(u>>12)&0xf])
511
+ t.putc(Hex[(u>>8)&0xf])
512
+ t.putc(Hex[(u>>4)&0xf])
513
+ t.putc(Hex[u&0xf])
514
+ end
515
+
516
+
517
+ def numenc(x)
518
+ if ((x.nan? || x.infinite?) rescue false)
519
+ raise Error, "Numeric cannot be represented: #{x}"
520
+ end
521
+ "#{x}"
522
+ end
523
+
524
+
525
+ # Decodes unicode character u from UTF-8
526
+ # bytes in string s at position i.
527
+ # Returns u and the number of bytes read.
528
+ def uchardec(s, i)
529
+ n = s.length - i
530
+ return [Ucharerr, 1] if n < 1
531
+
532
+ c0 = s[i].ord
533
+
534
+ # 1-byte, 7-bit sequence?
535
+ if c0 < Utagx
536
+ return [c0, 1]
537
+ end
538
+
539
+ # unexpected continuation byte?
540
+ return [Ucharerr, 1] if c0 < Utag2
541
+
542
+ # need continuation byte
543
+ return [Ucharerr, 1] if n < 2
544
+ c1 = s[i+1].ord
545
+ return [Ucharerr, 1] if c1 < Utagx || Utag2 <= c1
546
+
547
+ # 2-byte, 11-bit sequence?
548
+ if c0 < Utag3
549
+ u = (c0&Umask2)<<6 | (c1&Umaskx)
550
+ return [Ucharerr, 1] if u <= Uchar1max
551
+ return [u, 2]
552
+ end
553
+
554
+ # need second continuation byte
555
+ return [Ucharerr, 1] if n < 3
556
+ c2 = s[i+2].ord
557
+ return [Ucharerr, 1] if c2 < Utagx || Utag2 <= c2
558
+
559
+ # 3-byte, 16-bit sequence?
560
+ if c0 < Utag4
561
+ u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
562
+ return [Ucharerr, 1] if u <= Uchar2max
563
+ return [u, 3]
564
+ end
565
+
566
+ # need third continuation byte
567
+ return [Ucharerr, 1] if n < 4
568
+ c3 = s[i+3].ord
569
+ return [Ucharerr, 1] if c3 < Utagx || Utag2 <= c3
570
+
571
+ # 4-byte, 21-bit sequence?
572
+ if c0 < Utag5
573
+ u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
574
+ return [Ucharerr, 1] if u <= Uchar3max
575
+ return [u, 4]
576
+ end
577
+
578
+ return [Ucharerr, 1]
579
+ end
580
+
581
+
582
+ class Error < ::StandardError
583
+ end
584
+
585
+
586
+ Utagx = 0x80 # 1000 0000
587
+ Utag2 = 0xc0 # 1100 0000
588
+ Utag3 = 0xe0 # 1110 0000
589
+ Utag4 = 0xf0 # 1111 0000
590
+ Utag5 = 0xF8 # 1111 1000
591
+ Umaskx = 0x3f # 0011 1111
592
+ Umask2 = 0x1f # 0001 1111
593
+ Umask3 = 0x0f # 0000 1111
594
+ Umask4 = 0x07 # 0000 0111
595
+ Uchar1max = (1<<7) - 1
596
+ Uchar2max = (1<<11) - 1
597
+ Uchar3max = (1<<16) - 1
598
+ Ucharerr = 0xFFFD # unicode "replacement char"
599
+ Usurrself = 0x10000
600
+ Usurr1 = 0xd800
601
+ Usurr2 = 0xdc00
602
+ Usurr3 = 0xe000
603
+ Umax = 0x10ffff
604
+
605
+ Spc = ' '[0]
606
+ Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
607
+ Hex = '0123456789abcdef'
608
+ end
609
+ end
610
+ end
@@ -0,0 +1,25 @@
1
+ #
2
+ # Copyright 2012 Mortar Data Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module Mortar
18
+ class API
19
+ # client version
20
+ # see http://semver.org/
21
+ VERSION = "0.1.0"
22
+
23
+ SERVER_API_VERSION = "2"
24
+ end
25
+ end