heroku-api 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +14 -11
- data/Rakefile +1 -1
- data/changelog.txt +11 -0
- data/heroku-api.gemspec +1 -1
- data/lib/heroku-api.rb +1 -1
- data/lib/heroku/api.rb +14 -0
- data/lib/heroku/api/addons.rb +6 -5
- data/lib/heroku/api/apps.rb +1 -1
- data/lib/heroku/api/mock.rb +15 -1
- data/lib/heroku/api/mock/addons.rb +22 -22
- data/lib/heroku/api/mock/apps.rb +9 -9
- data/lib/heroku/api/mock/cache/get_user.json +1 -0
- data/lib/heroku/api/mock/collaborators.rb +4 -4
- data/lib/heroku/api/mock/config_vars.rb +3 -3
- data/lib/heroku/api/mock/domains.rb +3 -3
- data/lib/heroku/api/mock/keys.rb +1 -1
- data/lib/heroku/api/mock/logs.rb +1 -1
- data/lib/heroku/api/mock/processes.rb +12 -12
- data/lib/heroku/api/mock/releases.rb +9 -9
- data/lib/heroku/api/mock/stacks.rb +5 -5
- data/lib/heroku/api/mock/user.rb +16 -0
- data/lib/heroku/api/user.rb +14 -0
- data/lib/heroku/api/vendor/okjson.rb +120 -69
- data/lib/heroku/api/version.rb +1 -1
- data/test/test_addons.rb +13 -0
- data/test/test_apps.rb +13 -0
- data/test/test_processes.rb +2 -2
- data/test/test_user.rb +13 -0
- metadata +15 -10
@@ -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::
|
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::
|
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::
|
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::
|
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::
|
57
|
+
:body => Heroku::API::Mock.json_gzip('domain' => domain),
|
58
58
|
:status => 201
|
59
59
|
}
|
60
60
|
else
|
61
61
|
{
|
62
|
-
:body => Heroku::API::
|
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
|
data/lib/heroku/api/mock/keys.rb
CHANGED
@@ -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::
|
29
|
+
:body => Heroku::API::Mock.json_gzip(mock_data[:keys]),
|
30
30
|
:status => 200
|
31
31
|
}
|
32
32
|
end
|
data/lib/heroku/api/mock/logs.rb
CHANGED
@@ -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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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
|
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::
|
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::
|
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::
|
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
|
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
-
#
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
317
|
+
raise Error, "invalid escape char #{q[r]} in \"#{q}\""
|
305
318
|
end
|
306
319
|
when c == ?", c < Spc
|
307
|
-
raise
|
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
|
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
|
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
|
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|
|
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|
|
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
|
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
|
-
|
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
|
-
|
447
|
-
end
|
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
|
-
|
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
|