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.
- 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
|