heroku-api 0.1.0 → 0.1.1

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.
@@ -9,7 +9,7 @@ module Heroku
9
9
  with_mock_app(mock_data, app) do
10
10
  mock_data[:config_vars][app].delete(key)
11
11
  {
12
- :body => Heroku::API::OkJson.encode(mock_data[:config_vars][app]),
12
+ :body => Heroku::API::Mock.json_gzip(mock_data[:config_vars][app]),
13
13
  :status => 200
14
14
  }
15
15
  end
@@ -21,7 +21,7 @@ module Heroku
21
21
  app, _ = request_params[:captures][:path]
22
22
  with_mock_app(mock_data, app) do
23
23
  {
24
- :body => Heroku::API::OkJson.encode(mock_data[:config_vars][app]),
24
+ :body => Heroku::API::Mock.json_gzip(mock_data[:config_vars][app]),
25
25
  :status => 200
26
26
  }
27
27
  end
@@ -35,7 +35,7 @@ module Heroku
35
35
  new_config_vars = request_params[:body]
36
36
  mock_data[:config_vars][app].merge!(new_config_vars)
37
37
  {
38
- :body => Heroku::API::OkJson.encode(mock_data[:config_vars][app]),
38
+ :body => Heroku::API::Mock.json_gzip(mock_data[:config_vars][app]),
39
39
  :status => 200
40
40
  }
41
41
  end
@@ -29,7 +29,7 @@ module Heroku
29
29
  app, _ = request_params[:captures][:path]
30
30
  with_mock_app(mock_data, app) do |app_data|
31
31
  {
32
- :body => Heroku::API::OkJson.encode(mock_data[:domains][app]),
32
+ :body => Heroku::API::Mock.json_gzip(mock_data[:domains][app]),
33
33
  :status => 200
34
34
  }
35
35
  end
@@ -54,12 +54,12 @@ module Heroku
54
54
  }
55
55
  end
56
56
  {
57
- :body => Heroku::API::OkJson.encode('domain' => domain),
57
+ :body => Heroku::API::Mock.json_gzip('domain' => domain),
58
58
  :status => 201
59
59
  }
60
60
  else
61
61
  {
62
- :body => Heroku::API::OkJson.encode([["base","Please install the Custom Domains addon before adding domains to your app"]]),
62
+ :body => Heroku::API::Mock.json_gzip([["base","Please install the Custom Domains addon before adding domains to your app"]]),
63
63
  :status => 422
64
64
  }
65
65
  end
@@ -26,7 +26,7 @@ module Heroku
26
26
  Excon.stub(:expects => 200, :method => :get, :path => %r{^/user/keys}) do |params|
27
27
  request_params, mock_data = parse_stub_params(params)
28
28
  {
29
- :body => Heroku::API::OkJson.encode(mock_data[:keys]),
29
+ :body => Heroku::API::Mock.json_gzip(mock_data[:keys]),
30
30
  :status => 200
31
31
  }
32
32
  end
@@ -9,7 +9,7 @@ module Heroku
9
9
  with_mock_app(mock_data, app) do
10
10
  uuid = [SecureRandom.hex(4), SecureRandom.hex(2), SecureRandom.hex(2), SecureRandom.hex(2), SecureRandom.hex(6)].join('-')
11
11
  {
12
- :body => "https://logplex.heroku.com/sessions/#{uuid}?srv=#{Time.now.to_i}",
12
+ :body => Heroku::API::Mock.gzip("https://logplex.heroku.com/sessions/#{uuid}?srv=#{Time.now.to_i}"),
13
13
  :status => 200
14
14
  }
15
15
  end
@@ -8,7 +8,7 @@ module Heroku
8
8
  app, _ = request_params[:captures][:path]
9
9
  with_mock_app(mock_data, app) do |app_data|
10
10
  {
11
- :body => Heroku::API::OkJson.encode(get_mock_processes(mock_data, app)),
11
+ :body => Heroku::API::Mock.json_gzip(get_mock_processes(mock_data, app)),
12
12
  :status => 200
13
13
  }
14
14
  end
@@ -41,7 +41,7 @@ module Heroku
41
41
  }
42
42
  mock_data[:ps][app] << data
43
43
  {
44
- :body => Heroku::API::OkJson.encode(data),
44
+ :body => Heroku::API::Mock.json_gzip(data),
45
45
  :status => 200,
46
46
  }
47
47
  end
@@ -68,7 +68,7 @@ module Heroku
68
68
  end
69
69
  end
70
70
  {
71
- :body => 'ok',
71
+ :body => Heroku::API::Mock.gzip('ok'),
72
72
  :status => 200
73
73
  }
74
74
  end
@@ -105,18 +105,18 @@ module Heroku
105
105
  end
106
106
  end
107
107
  {
108
- :body => qty.to_s,
108
+ :body => Heroku::API::Mock.gzip(qty.to_s),
109
109
  :status => 200
110
110
  }
111
111
  else
112
112
  {
113
- :body => Heroku::API::OkJson.encode('error' => "No such type as #{type}") ,
113
+ :body => Heroku::API::Mock.json_gzip('error' => "No such type as #{type}") ,
114
114
  :status => 422
115
115
  }
116
116
  end
117
117
  else
118
118
  {
119
- :body => Heroku::API::OkJson.encode('error' => "That feature is not available on this app's stack"),
119
+ :body => Heroku::API::Mock.json_gzip('error' => "That feature is not available on this app's stack"),
120
120
  :status => 422
121
121
  }
122
122
  end
@@ -132,12 +132,12 @@ module Heroku
132
132
  type = request_params[:query].has_key?('type') && request_params[:query]['type']
133
133
  if !ps && !type
134
134
  {
135
- :body => Heroku::API::OkJson.encode({'error' => 'Missing process argument'}),
135
+ :body => Heroku::API::Mock.json_gzip({'error' => 'Missing process argument'}),
136
136
  :status => 422
137
137
  }
138
138
  else
139
139
  {
140
- :body => 'ok',
140
+ :body => Heroku::API::Mock.gzip('ok'),
141
141
  :status => 200
142
142
  }
143
143
  end
@@ -153,12 +153,12 @@ module Heroku
153
153
  unless app_data['stack'] == 'cedar'
154
154
  app_data['dynos'] = dynos
155
155
  {
156
- :body => Heroku::API::OkJson.encode({'name' => app, 'dynos' => dynos}),
156
+ :body => Heroku::API::Mock.json_gzip({'name' => app, 'dynos' => dynos}),
157
157
  :status => 200
158
158
  }
159
159
  else
160
160
  {
161
- :body => Heroku::API::OkJson.encode({'error' => "For Cedar apps, use `heroku scale web=#{dynos}`"}),
161
+ :body => Heroku::API::Mock.json_gzip({'error' => "For Cedar apps, use `heroku scale web=#{dynos}`"}),
162
162
  :status => 422
163
163
  }
164
164
  end
@@ -174,12 +174,12 @@ module Heroku
174
174
  unless app_data['stack'] == 'cedar'
175
175
  app_data['workers'] = workers
176
176
  {
177
- :body => Heroku::API::OkJson.encode({'name' => app, 'workers' => workers}),
177
+ :body => Heroku::API::Mock.json_gzip({'name' => app, 'workers' => workers}),
178
178
  :status => 200
179
179
  }
180
180
  else
181
181
  {
182
- :body => Heroku::API::OkJson.encode({'error' => "For Cedar apps, use `heroku scale worker=#{workers}`"}),
182
+ :body => Heroku::API::Mock.json_gzip({'error' => "For Cedar apps, use `heroku scale worker=#{workers}`"}),
183
183
  :status => 422
184
184
  }
185
185
  end
@@ -9,17 +9,17 @@ module Heroku
9
9
  with_mock_app(mock_data, app) do |app_data|
10
10
  if get_mock_app_addon(mock_data, app, 'releases:basic')
11
11
  {
12
- :body => mock_data[:releases][app][-2..-1],
12
+ :body => Heroku::API::Mock.json_gzip(mock_data[:releases][app][-2..-1]),
13
13
  :status => 200
14
14
  }
15
15
  elsif get_mock_app_addon(mock_data, app, 'releases:advanced')
16
16
  {
17
- :body => mock_data[:releases][app],
17
+ :body => Heroku::API::Mock.json_gzip(mock_data[:releases][app]),
18
18
  :status => 200
19
19
  }
20
20
  else
21
21
  {
22
- :body => Heroku::API::OkJson.encode({'error' => 'Please install the Release Management add-on to access release history'}),
22
+ :body => Heroku::API::Mock.json_gzip({'error' => 'Please install the Release Management add-on to access release history'}),
23
23
  :status => 422
24
24
  }
25
25
  end
@@ -39,7 +39,7 @@ module Heroku
39
39
  end
40
40
  if release_data = releases.detect {|release| release['name'] == release_name}
41
41
  {
42
- :body => Heroku::API::OkJson.encode(release_data),
42
+ :body => Heroku::API::Mock.json_gzip(release_data),
43
43
  :status => 200
44
44
  }
45
45
  else
@@ -50,7 +50,7 @@ module Heroku
50
50
  end
51
51
  else
52
52
  {
53
- :body => Heroku::API::OkJson.encode({'error' => 'Please install the Release Management add-on to access release history'}),
53
+ :body => Heroku::API::Mock.json_gzip({'error' => 'Please install the Release Management add-on to access release history'}),
54
54
  :status => 422
55
55
  }
56
56
  end
@@ -95,24 +95,24 @@ module Heroku
95
95
  }
96
96
 
97
97
  {
98
- :body => release_data['name'],
98
+ :body => Heroku::API::Mock.gzip(release_data['name']),
99
99
  :status => 200
100
100
  }
101
101
  else
102
102
  {
103
- :body => Heroku::API::OkJson.encode({'error' => 'Cannot rollback to a release that had a different set of addons installed'}),
103
+ :body => Heroku::API::Mock.json_gzip({'error' => 'Cannot rollback to a release that had a different set of addons installed'}),
104
104
  :status => 422
105
105
  }
106
106
  end
107
107
  else
108
108
  {
109
- :body => 'Record not found.',
109
+ :body => Heroku::API::Mock.gzip('Record not found.'),
110
110
  :status => 404
111
111
  }
112
112
  end
113
113
  else
114
114
  {
115
- :body => Heroku::API::OkJson.encode({'error' => 'Please install the Release Management add-on to access release history'}),
115
+ :body => Heroku::API::Mock.json_gzip({'error' => 'Please install the Release Management add-on to access release history'}),
116
116
  :status => 422
117
117
  }
118
118
  end
@@ -34,10 +34,10 @@ module Heroku
34
34
  request_params, mock_data = parse_stub_params(params)
35
35
  app, _ = request_params[:captures][:path]
36
36
  with_mock_app(mock_data, app) do |app_data|
37
- stack_data = STACKS.dup
37
+ stack_data = Marshal::load(Marshal.dump(STACKS))
38
38
  stack_data.detect {|stack| stack['name'] == app_data['stack']}['current'] = true
39
39
  {
40
- :body => Heroku::API::OkJson.encode(stack_data),
40
+ :body => Heroku::API::Mock.json_gzip(stack_data),
41
41
  :status => 200
42
42
  }
43
43
  end
@@ -52,7 +52,7 @@ module Heroku
52
52
  if app_data['stack'] != 'cedar' && stack != 'cedar'
53
53
  if STACKS.map {|stack_data| stack_data['name']}.include?(stack)
54
54
  {
55
- :body => <<-BODY,
55
+ :body => Heroku::API::Mock.gzip(<<-BODY),
56
56
  HTTP/1.1 200 OK
57
57
  -----> Preparing to migrate #{app}
58
58
  #{app_data['stack']} -> #{stack}
@@ -66,13 +66,13 @@ BODY
66
66
  }
67
67
  else
68
68
  {
69
- :body => Heroku::API::OkJson.encode('error' => 'Stack not found'),
69
+ :body => Heroku::API::Mock.json_gzip('error' => 'Stack not found'),
70
70
  :status => 404
71
71
  }
72
72
  end
73
73
  else
74
74
  {
75
- :body => Heroku::API::OkJson.encode('error' => 'Stack migration to/from Cedar is not available. Create a new app with --stack cedar instead.'),
75
+ :body => Heroku::API::Mock.json_gzip('error' => 'Stack migration to/from Cedar is not available. Create a new app with --stack cedar instead.'),
76
76
  :status => 422
77
77
  }
78
78
  end
@@ -0,0 +1,16 @@
1
+ module Heroku
2
+ class API
3
+ module Mock
4
+
5
+ # stub GET /user
6
+ Excon.stub(:expects => 200, :method => :get, :path => %r{^/user}) do |params|
7
+ request_params, mock_data = parse_stub_params(params)
8
+ {
9
+ :body => Heroku::API::Mock.gzip(File.read("#{File.dirname(__FILE__)}/cache/get_user.json")),
10
+ :status => 200
11
+ }
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ module Heroku
2
+ class API
3
+
4
+ # GET /user
5
+ def get_user
6
+ request(
7
+ :expects => 200,
8
+ :method => :get,
9
+ :path => "/user"
10
+ )
11
+ end
12
+
13
+ end
14
+ end
@@ -1,4 +1,6 @@
1
- # Copyright 2011 Keith Rarick
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright 2011, 2012 Keith Rarick
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
6
  # of this software and associated documentation files (the "Software"), to deal
@@ -30,7 +32,6 @@ module Heroku
30
32
  module OkJson
31
33
  extend self
32
34
 
33
- class ParserError < ::StandardError; end
34
35
 
35
36
  # Decodes a json document in string s and
36
37
  # returns the corresponding ruby value.
@@ -44,7 +45,7 @@ module Heroku
44
45
  ts = lex(s)
45
46
  v, ts = textparse(ts)
46
47
  if ts.length > 0
47
- raise Heroku::API::OkJson::ParserError, 'trailing garbage'
48
+ raise Error, 'trailing garbage'
48
49
  end
49
50
  v
50
51
  end
@@ -56,14 +57,15 @@ module Heroku
56
57
  # except that it does not accept atomic values.
57
58
  def textparse(ts)
58
59
  if ts.length < 0
59
- raise Heroku::API::OkJson::ParserError, 'empty'
60
+ raise Error, 'empty'
60
61
  end
61
62
 
62
63
  typ, _, val = ts[0]
63
64
  case typ
64
65
  when '{' then objparse(ts)
65
66
  when '[' then arrparse(ts)
66
- else valparse(ts)
67
+ else
68
+ raise Error, "unexpected #{val.inspect}"
67
69
  end
68
70
  end
69
71
 
@@ -72,7 +74,7 @@ module Heroku
72
74
  # Returns the parsed value and any trailing tokens.
73
75
  def valparse(ts)
74
76
  if ts.length < 0
75
- raise Heroku::API::OkJson::ParserError, 'empty'
77
+ raise Error, 'empty'
76
78
  end
77
79
 
78
80
  typ, _, val = ts[0]
@@ -81,7 +83,7 @@ module Heroku
81
83
  when '[' then arrparse(ts)
82
84
  when :val,:str then [val, ts[1..-1]]
83
85
  else
84
- raise Heroku::API::OkJson::ParserError, "unexpected #{val.inspect}"
86
+ raise Error, "unexpected #{val.inspect}"
85
87
  end
86
88
  end
87
89
 
@@ -117,11 +119,11 @@ module Heroku
117
119
 
118
120
 
119
121
  # Parses a "member" in the sense of RFC 4627.
120
- # Returns the parsed value and any trailing tokens.
122
+ # Returns the parsed values and any trailing tokens.
121
123
  def pairparse(ts)
122
124
  (typ, _, k), ts = ts[0], ts[1..-1]
123
125
  if typ != :str
124
- raise Heroku::API::OkJson::ParserError, "unexpected #{k.inspect}"
126
+ raise Error, "unexpected #{k.inspect}"
125
127
  end
126
128
  ts = eat(':', ts)
127
129
  v, ts = valparse(ts)
@@ -161,20 +163,20 @@ module Heroku
161
163
 
162
164
  def eat(typ, ts)
163
165
  if ts[0][0] != typ
164
- raise Heroku::API::OkJson::ParserError, "expected #{typ} (got #{ts[0].inspect})"
166
+ raise Error, "expected #{typ} (got #{ts[0].inspect})"
165
167
  end
166
168
  ts[1..-1]
167
169
  end
168
170
 
169
171
 
170
- # Sans s and returns a list of json tokens,
172
+ # Scans s and returns a list of json tokens,
171
173
  # excluding white space (as defined in RFC 4627).
172
174
  def lex(s)
173
175
  ts = []
174
176
  while s.length > 0
175
177
  typ, lexeme, val = tok(s)
176
178
  if typ == nil
177
- raise Heroku::API::OkJson::ParserError, "invalid character at #{s[0,10].inspect}"
179
+ raise Error, "invalid character at #{s[0,10].inspect}"
178
180
  end
179
181
  if typ != :space
180
182
  ts << [typ, lexeme, val]
@@ -187,7 +189,7 @@ module Heroku
187
189
 
188
190
  # Scans the first token in s and
189
191
  # returns a 3-element list, or nil
190
- # if no such token exists.
192
+ # if s does not begin with a valid token.
191
193
  #
192
194
  # The first list element is one of
193
195
  # '{', '}', ':', ',', '[', ']',
@@ -241,7 +243,7 @@ module Heroku
241
243
  def strtok(s)
242
244
  m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
243
245
  if ! m
244
- raise Heroku::API::OkJson::ParserError, "invalid string literal at #{abbrev(s)}"
246
+ raise Error, "invalid string literal at #{abbrev(s)}"
245
247
  end
246
248
  [:str, m[0], unquote(m[0])]
247
249
  end
@@ -258,10 +260,16 @@ module Heroku
258
260
 
259
261
  # Converts a quoted json string literal q into a UTF-8-encoded string.
260
262
  # The rules are different than for Ruby, so we cannot use eval.
261
- # Unquote will raise Heroku::API::OkJson::ParserError, an error if q contains control characters.
263
+ # Unquote will raise an error if q contains control characters.
262
264
  def unquote(q)
263
265
  q = q[1...-1]
264
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
265
273
  r, w = 0, 0
266
274
  while r < q.length
267
275
  c = q[r]
@@ -269,7 +277,7 @@ module Heroku
269
277
  when c == ?\\
270
278
  r += 1
271
279
  if r >= q.length
272
- raise Heroku::API::OkJson::ParserError, "string literal ends with a \"\\\": \"#{q}\""
280
+ raise Error, "string literal ends with a \"\\\": \"#{q}\""
273
281
  end
274
282
 
275
283
  case q[r]
@@ -286,7 +294,7 @@ module Heroku
286
294
  uchar = begin
287
295
  hexdec4(q[r,4])
288
296
  rescue RuntimeError => e
289
- raise Heroku::API::OkJson::ParserError, "invalid escape sequence \\u#{q[r,4]}: #{e}"
297
+ raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
290
298
  end
291
299
  r += 4
292
300
  if surrogate? uchar
@@ -299,16 +307,23 @@ module Heroku
299
307
  end
300
308
  end
301
309
  end
302
- w += ucharenc(a, w, uchar)
310
+ if rubydoesenc
311
+ a[w] = '' << uchar
312
+ w += 1
313
+ else
314
+ w += ucharenc(a, w, uchar)
315
+ end
303
316
  else
304
- raise Heroku::API::OkJson::ParserError, "invalid escape char #{q[r]} in \"#{q}\""
317
+ raise Error, "invalid escape char #{q[r]} in \"#{q}\""
305
318
  end
306
319
  when c == ?", c < Spc
307
- raise Heroku::API::OkJson::ParserError, "invalid character in string literal \"#{q}\""
320
+ raise Error, "invalid character in string literal \"#{q}\""
308
321
  else
309
322
  # Copy anything else byte-for-byte.
310
323
  # Valid UTF-8 will remain valid UTF-8.
311
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.
312
327
  a[w] = c
313
328
  r += 1
314
329
  w += 1
@@ -318,9 +333,36 @@ module Heroku
318
333
  end
319
334
 
320
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
+
321
363
  def hexdec4(s)
322
364
  if s.length != 4
323
- raise Heroku::API::OkJson::ParserError, 'short'
365
+ raise Error, 'short'
324
366
  end
325
367
  (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
326
368
  end
@@ -354,7 +396,7 @@ module Heroku
354
396
  when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
355
397
  when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
356
398
  else
357
- raise Heroku::API::OkJson::ParserError, "invalid hex code #{c}"
399
+ raise Error, "invalid hex code #{c}"
358
400
  end
359
401
  end
360
402
 
@@ -362,32 +404,53 @@ module Heroku
362
404
  # Encodes x into a json text. It may contain only
363
405
  # Array, Hash, String, Numeric, true, false, nil.
364
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.
365
412
  # Strings contained in x must be valid UTF-8.
366
- # Values that cannot be represented, such as
367
- # Nan, Infinity, Symbol, and Proc, are encoded
368
- # as null, in accordance with ECMA-262, 5th ed.
369
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)
370
424
  case x
371
425
  when Hash then objenc(x)
372
426
  when Array then arrenc(x)
373
427
  when String then strenc(x)
374
428
  when Numeric then numenc(x)
375
- when Symbol then strenc(x.to_s)
376
429
  when true then "true"
377
430
  when false then "false"
378
431
  when nil then "null"
379
- else "null"
432
+ else
433
+ raise Error, "cannot encode #{x.class}: #{x.inspect}"
380
434
  end
381
435
  end
382
436
 
383
437
 
384
438
  def objenc(x)
385
- '{' + x.map{|k,v| encode(k) + ':' + encode(v)}.join(',') + '}'
439
+ '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
386
440
  end
387
441
 
388
442
 
389
443
  def arrenc(a)
390
- '[' + a.map{|x| encode(x)}.join(',') + ']'
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
391
454
  end
392
455
 
393
456
 
@@ -395,6 +458,10 @@ module Heroku
395
458
  t = StringIO.new
396
459
  t.putc(?")
397
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
+
398
465
  while r < s.length
399
466
  case s[r]
400
467
  when ?" then t.print('\\"')
@@ -409,21 +476,13 @@ module Heroku
409
476
  case true
410
477
  when Spc <= c && c <= ?~
411
478
  t.putc(c)
412
- when true
479
+ when rubydoesenc
480
+ u = c.ord
481
+ surrenc(t, u)
482
+ else
413
483
  u, size = uchardec(s, r)
414
484
  r += size - 1 # we add one more at the bottom of the loop
415
- if u < 0x10000
416
- t.print('\\u')
417
- hexenc4(t, u)
418
- else
419
- u1, u2 = unsubst(u)
420
- t.print('\\u')
421
- hexenc4(t, u1)
422
- t.print('\\u')
423
- hexenc4(t, u2)
424
- end
425
- else
426
- # invalid byte; skip it
485
+ surrenc(t, u)
427
486
  end
428
487
  end
429
488
  r += 1
@@ -433,6 +492,20 @@ module Heroku
433
492
  end
434
493
 
435
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
+
436
509
  def hexenc4(t, u)
437
510
  t.putc(Hex[(u>>12)&0xf])
438
511
  t.putc(Hex[(u>>8)&0xf])
@@ -442,9 +515,9 @@ module Heroku
442
515
 
443
516
 
444
517
  def numenc(x)
445
- if x.nan? || x.infinite?
446
- return 'null'
447
- end rescue nil
518
+ if ((x.nan? || x.infinite?) rescue false)
519
+ raise Error, "Numeric cannot be represented: #{x}"
520
+ end
448
521
  "#{x}"
449
522
  end
450
523
 
@@ -506,32 +579,10 @@ module Heroku
506
579
  end
507
580
 
508
581
 
509
- # Encodes unicode character u as UTF-8
510
- # bytes in string a at position i.
511
- # Returns the number of bytes written.
512
- def ucharenc(a, i, u)
513
- case true
514
- when u <= Uchar1max
515
- a[i] = (u & 0xff).chr
516
- 1
517
- when u <= Uchar2max
518
- a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
519
- a[i+1] = (Utagx | (u&Umaskx)).chr
520
- 2
521
- when u <= Uchar3max
522
- a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
523
- a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
524
- a[i+2] = (Utagx | (u&Umaskx)).chr
525
- 3
526
- else
527
- a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
528
- a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
529
- a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
530
- a[i+3] = (Utagx | (u&Umaskx)).chr
531
- 4
532
- end
582
+ class Error < ::StandardError
533
583
  end
534
584
 
585
+
535
586
  Utagx = 0x80 # 1000 0000
536
587
  Utag2 = 0xc0 # 1100 0000
537
588
  Utag3 = 0xe0 # 1110 0000