mortar-api-ruby 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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