jsonnet 0.1.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.clang-format +11 -0
- data/.travis.yml +19 -0
- data/README.md +72 -13
- data/ext/jsonnet/callbacks.c +241 -0
- data/ext/jsonnet/extconf.rb +56 -0
- data/ext/jsonnet/helpers.c +74 -0
- data/ext/jsonnet/jsonnet.c +4 -357
- data/ext/jsonnet/jsonnet_values.c +206 -0
- data/ext/jsonnet/ruby_jsonnet.h +39 -0
- data/ext/jsonnet/vm.c +508 -0
- data/jsonnet.gemspec +6 -4
- data/lib/jsonnet.rb +42 -2
- data/lib/jsonnet/version.rb +1 -1
- data/lib/jsonnet/vm.rb +127 -8
- data/test/fixtures/jpath.libsonnet +3 -0
- data/test/test_jsonnet.rb +44 -1
- data/test/test_vm.rb +313 -10
- metadata +37 -16
data/jsonnet.gemspec
CHANGED
@@ -19,8 +19,10 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
spec.
|
23
|
-
|
24
|
-
spec.add_development_dependency "
|
25
|
-
spec.add_development_dependency "rake
|
22
|
+
spec.add_runtime_dependency "mini_portile2", ">= 2.2.0"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", ">= 1.7"
|
25
|
+
spec.add_development_dependency "rake", ">= 10.0"
|
26
|
+
spec.add_development_dependency "test-unit", ">= 3.1.3"
|
27
|
+
spec.add_development_dependency "rake-compiler", ">= 0.9.5"
|
26
28
|
end
|
data/lib/jsonnet.rb
CHANGED
@@ -1,7 +1,47 @@
|
|
1
1
|
require "jsonnet/version"
|
2
|
-
require "jsonnet/jsonnet_wrap"
|
3
2
|
require "jsonnet/vm"
|
3
|
+
require "json"
|
4
4
|
|
5
5
|
module Jsonnet
|
6
|
-
|
6
|
+
module_function
|
7
|
+
|
8
|
+
##
|
9
|
+
# Evaluates a string of Jsonnet and returns a hash of the resulting JSON
|
10
|
+
#
|
11
|
+
# @param [String] jsonnet Jsonnet source string, ideally in UTF-8 encoding
|
12
|
+
# @param [Hash] jsonnet_options A hash of options to for Jsonnet::VM.
|
13
|
+
# Available options are: filename, multi,
|
14
|
+
# import_callback, gc_growth_triger,
|
15
|
+
# gc_min_objects, max_stack, max_trace
|
16
|
+
# @param [Hash] json_options Options supported by {JSON.parse}[http://www.rubydoc.info/github/flori/json/JSON#parse-class_method]
|
17
|
+
# @return [Hash] The JSON representation as a hash
|
18
|
+
# @raise [UnsupportedOptionError] Raised when an option passed is unsupported by Jsonnet::VM
|
19
|
+
#
|
20
|
+
# @note This method runs Jsonnet::VM#evaluate and runs the string
|
21
|
+
# output through {JSON.parse}[http://www.rubydoc.info/github/flori/json/JSON#parse-class_method]
|
22
|
+
# so those should be looked at for furhter details
|
23
|
+
def evaluate(jsonnet, jsonnet_options: {}, json_options: {})
|
24
|
+
output = VM.evaluate(jsonnet, jsonnet_options)
|
25
|
+
JSON.parse(output, json_options)
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Loads a Jsonnet file and returns a hash of the resulting JSON
|
30
|
+
#
|
31
|
+
# @param [String] path path to the jsonnet file
|
32
|
+
# @param [Hash] jsonnet_options A hash of options to for Jsonnet::VM.
|
33
|
+
# Available options are: encoding, multi,
|
34
|
+
# import_callback, gc_growth_triger,
|
35
|
+
# gc_min_objects, max_stack, max_trace
|
36
|
+
# @param [Hash] json_options Options supported by {JSON.parse}[http://www.rubydoc.info/github/flori/json/JSON#parse-class_method]
|
37
|
+
# @return [Hash] The JSON representation as a hash
|
38
|
+
# @raise [UnsupportedOptionError] Raised when an option passed is unsupported by Jsonnet::VM
|
39
|
+
#
|
40
|
+
# @note This method runs Jsonnet::VM#evaluate_file and runs the string
|
41
|
+
# output through {JSON.parse}[http://www.rubydoc.info/github/flori/json/JSON#parse-class_method]
|
42
|
+
# so those should be looked at for furhter details
|
43
|
+
def load(path, jsonnet_options: {}, json_options: {})
|
44
|
+
output = VM.evaluate_file(path, jsonnet_options)
|
45
|
+
JSON.parse(output, json_options)
|
46
|
+
end
|
7
47
|
end
|
data/lib/jsonnet/version.rb
CHANGED
data/lib/jsonnet/vm.rb
CHANGED
@@ -1,5 +1,59 @@
|
|
1
|
+
require "jsonnet/jsonnet_wrap"
|
2
|
+
|
1
3
|
module Jsonnet
|
2
4
|
class VM
|
5
|
+
class << self
|
6
|
+
##
|
7
|
+
# Convenient method to evaluate a Jsonnet snippet.
|
8
|
+
#
|
9
|
+
# It implicitly instantiates a VM and then evaluate Jsonnet with the VM.
|
10
|
+
#
|
11
|
+
# @param snippet [String] Jsonnet source string.
|
12
|
+
# @param options [Hash] options to {.new} or options to {#evaluate}
|
13
|
+
# @return [String]
|
14
|
+
# @see #evaluate
|
15
|
+
def evaluate(snippet, options = {})
|
16
|
+
snippet_check = ->(key, value) { key.to_s.match(/^filename|multi$/) }
|
17
|
+
snippet_options = options.select(&snippet_check)
|
18
|
+
vm_options = options.reject(&snippet_check)
|
19
|
+
new(vm_options).evaluate(snippet, **snippet_options)
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Convenient method to evaluate a Jsonnet file.
|
24
|
+
#
|
25
|
+
# It implicitly instantiates a VM and then evaluates Jsonnet with the VM.
|
26
|
+
#
|
27
|
+
# @param filename [String] Jsonnet source file.
|
28
|
+
# @param options [Hash] options to {.new} or options to {#evaluate_file}
|
29
|
+
# @return [String]
|
30
|
+
# @see #evaluate_file
|
31
|
+
def evaluate_file(filename, options = {})
|
32
|
+
file_check = ->(key, value) { key.to_s.match(/^encoding|multi$/) }
|
33
|
+
file_options = options.select(&file_check)
|
34
|
+
vm_options = options.reject(&file_check)
|
35
|
+
new(vm_options).evaluate_file(filename, **file_options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# initializes a new VM with the given configuration.
|
41
|
+
#
|
42
|
+
# @param [Hash] options a mapping from option names to their values.
|
43
|
+
# It can have names of writable attributes in VM class as keys.
|
44
|
+
# @return [VM] the VM.
|
45
|
+
def initialize(options = {})
|
46
|
+
options.each do |key, value|
|
47
|
+
method = "#{key}="
|
48
|
+
if respond_to?(method)
|
49
|
+
public_send(method, value)
|
50
|
+
else
|
51
|
+
raise UnsupportedOptionError.new("Jsonnet VM does not support #{key} option")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
3
57
|
##
|
4
58
|
# Evaluates Jsonnet source.
|
5
59
|
#
|
@@ -11,9 +65,9 @@ module Jsonnet
|
|
11
65
|
# @raise [EvaluationError] raised when the evaluation results an error.
|
12
66
|
# @raise [UnsupportedEncodingError] raised when the encoding of jsonnet
|
13
67
|
# is not ASCII-compatible.
|
14
|
-
# @note It is recommended to encode the source string in UTF-8 because
|
15
|
-
# Jsonnet expects it is ASCII-compatible, the result JSON string
|
16
|
-
# shall be UTF-{8,16,32} according to RFC 7159 thus the only
|
68
|
+
# @note It is recommended to encode the source string in UTF-8 because
|
69
|
+
# Jsonnet expects it is ASCII-compatible, the result JSON string
|
70
|
+
# shall be UTF-{8,16,32} according to RFC 7159 thus the only
|
17
71
|
# intersection between the requirements is UTF-8.
|
18
72
|
def evaluate(jsonnet, filename: "(jsonnet)", multi: false)
|
19
73
|
eval_snippet(jsonnet, filename, multi)
|
@@ -26,23 +80,88 @@ module Jsonnet
|
|
26
80
|
# @param [Boolean] multi enables multi-mode
|
27
81
|
# @return [String] a JSON representation of the evaluation result
|
28
82
|
# @raise [EvaluationError] raised when the evaluation results an error.
|
29
|
-
# @note It is recommended to encode the source file in UTF-8 because
|
30
|
-
# Jsonnet expects it is ASCII-compatible, the result JSON string
|
31
|
-
# shall be UTF-{8,16,32} according to RFC 7159 thus the only
|
83
|
+
# @note It is recommended to encode the source file in UTF-8 because
|
84
|
+
# Jsonnet expects it is ASCII-compatible, the result JSON string
|
85
|
+
# shall be UTF-{8,16,32} according to RFC 7159 thus the only
|
32
86
|
# intersection between the requirements is UTF-8.
|
33
87
|
def evaluate_file(filename, encoding: Encoding.default_external, multi: false)
|
34
88
|
eval_file(filename, encoding, multi)
|
35
89
|
end
|
36
90
|
|
91
|
+
##
|
92
|
+
# Format Jsonnet file.
|
93
|
+
#
|
94
|
+
# @param [String] filename filename of a Jsonnet source file.
|
95
|
+
# @return [String] a formatted Jsonnet representation
|
96
|
+
# @raise [FormatError] raised when the formatting results an error.
|
97
|
+
def format_file(filename, encoding: Encoding.default_external)
|
98
|
+
fmt_file(filename, encoding)
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Format Jsonnet snippet.
|
103
|
+
#
|
104
|
+
# @param [String] jsonnet Jsonnet source string. Must be encoded in ASCII-compatible encoding.
|
105
|
+
# @param [String] filename filename of the source. Used in stacktrace.
|
106
|
+
# @return [String] a formatted Jsonnet representation
|
107
|
+
# @raise [FormatError] raised when the formatting results an error.
|
108
|
+
# @raise [UnsupportedEncodingError] raised when the encoding of jsonnt is not ASCII-compatible.
|
109
|
+
def format(jsonnet, filename: "(jsonnet)")
|
110
|
+
fmt_snippet(jsonnet, filename)
|
111
|
+
end
|
112
|
+
|
37
113
|
##
|
38
114
|
# Lets the given block handle "import" expression of Jsonnet.
|
39
115
|
# @yieldparam [String] base base path to resolve "rel" from.
|
40
116
|
# @yieldparam [String] rel a relative or absolute path to the file to be imported
|
41
117
|
# @yieldreturn [Array<String>] a pair of the content of the imported file and
|
42
118
|
# its path.
|
43
|
-
def handle_import
|
44
|
-
|
119
|
+
def handle_import(&block)
|
120
|
+
if block.nil?
|
121
|
+
raise ArgumentError, 'handle_import requires a block'
|
122
|
+
end
|
123
|
+
self.import_callback = to_method(block)
|
45
124
|
nil
|
46
125
|
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Define a function (native extension) in the VM and let the given block
|
129
|
+
# handle the invocation of the function.
|
130
|
+
#
|
131
|
+
# @param name [Symbol|String] name of the function.
|
132
|
+
# Must be a valid identifier in Jsonnet.
|
133
|
+
# @param body [#to_proc] body of the function.
|
134
|
+
# @yield calls the given block instead of `body` if `body` is `nil`
|
135
|
+
#
|
136
|
+
# @note Currently it cannot define keyword or optional paramters in Jsonnet.
|
137
|
+
# Also all the positional optional parameters of the body are interpreted
|
138
|
+
# as required parameters. And the body cannot have keyword, rest or
|
139
|
+
# keyword rest paramters.
|
140
|
+
def define_function(name, body = nil, &block)
|
141
|
+
body = body ? body.to_proc : block
|
142
|
+
if body.nil?
|
143
|
+
raise ArgumentError, 'define_function requires a body argument or a block'
|
144
|
+
end
|
145
|
+
params = body.parameters.map.with_index do |(type, name), i|
|
146
|
+
raise ArgumentError, "rest or keyword parameters are not allowed: #{type}" \
|
147
|
+
unless [:req, :opt].include? type
|
148
|
+
|
149
|
+
name || "p#{i}"
|
150
|
+
end
|
151
|
+
|
152
|
+
register_native_callback(name.to_sym, to_method(body), params);
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
# Wraps the function body with a method so that `break` and `return`
|
157
|
+
# behave like `return` as they do in a body of Module#define_method.
|
158
|
+
def to_method(body)
|
159
|
+
mod = Module.new {
|
160
|
+
define_method(:dummy, body)
|
161
|
+
}
|
162
|
+
mod.instance_method(:dummy).bind(body.binding.receiver)
|
163
|
+
end
|
164
|
+
|
165
|
+
class UnsupportedOptionError < RuntimeError; end
|
47
166
|
end
|
48
167
|
end
|
data/test/test_jsonnet.rb
CHANGED
@@ -1,8 +1,51 @@
|
|
1
1
|
require 'jsonnet'
|
2
|
+
|
3
|
+
require 'tempfile'
|
2
4
|
require 'test/unit'
|
3
5
|
|
4
6
|
class TestJsonnet < Test::Unit::TestCase
|
5
7
|
test 'libversion returns a String' do
|
6
|
-
assert_kind_of String, Jsonnet.libversion
|
8
|
+
assert_kind_of String, Jsonnet.libversion
|
9
|
+
end
|
10
|
+
|
11
|
+
test 'Jsonnet.evaluate returns a JSON parsed result' do
|
12
|
+
result = Jsonnet.evaluate('{ foo: "bar" }')
|
13
|
+
assert_equal result, { "foo" => "bar" }
|
14
|
+
end
|
15
|
+
|
16
|
+
test 'Jsonnet.evaluate can accept options for JSON' do
|
17
|
+
result = Jsonnet.evaluate('{ foo: "bar" }', json_options: { symbolize_names: true })
|
18
|
+
assert_equal result, { foo: "bar" }
|
19
|
+
end
|
20
|
+
|
21
|
+
test 'Jsonnet.evaluate can accept options for Jsonnet VM' do
|
22
|
+
result = Jsonnet.evaluate(
|
23
|
+
'import "imported.jsonnet"',
|
24
|
+
jsonnet_options: {
|
25
|
+
import_callback: ->(_base, _rel) do
|
26
|
+
return ['{ foo: "bar" }', 'imported']
|
27
|
+
end
|
28
|
+
}
|
29
|
+
)
|
30
|
+
assert_equal result, { "foo" => "bar" }
|
31
|
+
end
|
32
|
+
|
33
|
+
test 'Jsonnet.load returns a JSON parsed result' do
|
34
|
+
result = Jsonnet.load(example_jsonnet_file.path)
|
35
|
+
assert_equal result, { "foo1" => 1 }
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def example_jsonnet_file
|
41
|
+
Tempfile.open("example.jsonnet") do |f|
|
42
|
+
f.write %<
|
43
|
+
local myvar = 1;
|
44
|
+
{
|
45
|
+
["foo" + myvar]: myvar,
|
46
|
+
}
|
47
|
+
>
|
48
|
+
f
|
49
|
+
end
|
7
50
|
end
|
8
51
|
end
|
data/test/test_vm.rb
CHANGED
@@ -47,7 +47,7 @@ class TestVM < Test::Unit::TestCase
|
|
47
47
|
vm = Jsonnet::VM.new
|
48
48
|
begin
|
49
49
|
with_example_file(%q{ ["unterminated string }) {|fname|
|
50
|
-
vm.evaluate_file(fname.encode(Encoding::SJIS))
|
50
|
+
vm.evaluate_file(fname.encode(Encoding::SJIS))
|
51
51
|
}
|
52
52
|
rescue Jsonnet::EvaluationError => e
|
53
53
|
assert_equal Encoding::SJIS, e.message.encoding
|
@@ -104,19 +104,62 @@ class TestVM < Test::Unit::TestCase
|
|
104
104
|
test "Jsonnet::VM#evaluate raises an error in the encoding of filename" do
|
105
105
|
vm = Jsonnet::VM.new
|
106
106
|
begin
|
107
|
-
vm.evaluate(%Q{ ["unterminated string }, filename: "テスト.json".encode(Encoding::SJIS))
|
107
|
+
vm.evaluate(%Q{ ["unterminated string }, filename: "テスト.json".encode(Encoding::SJIS))
|
108
108
|
rescue Jsonnet::EvaluationError => e
|
109
109
|
assert_equal Encoding::SJIS, e.message.encoding
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
113
|
-
test "Jsonnet::VM#ext_var binds a variable" do
|
113
|
+
test "Jsonnet::VM#ext_var binds a variable to a string value" do
|
114
114
|
vm = Jsonnet::VM.new
|
115
115
|
vm.ext_var("var1", "foo")
|
116
116
|
result = vm.evaluate('[std.extVar("var1")]')
|
117
117
|
assert_equal JSON.parse('["foo"]'), JSON.parse(result)
|
118
118
|
end
|
119
119
|
|
120
|
+
test "Jsonnet::VM#ext_code binds a variable to a code fragment" do
|
121
|
+
vm = Jsonnet::VM.new
|
122
|
+
vm.ext_code("var1", "{a:1}")
|
123
|
+
result = vm.evaluate('[std.extVar("var1")]')
|
124
|
+
assert_equal JSON.parse(<<-EOS), JSON.parse(result)
|
125
|
+
[
|
126
|
+
{
|
127
|
+
"a": 1
|
128
|
+
}
|
129
|
+
]
|
130
|
+
EOS
|
131
|
+
end
|
132
|
+
|
133
|
+
test "Jsonnet::VM#tla_var binds a top-level variable to a string value" do
|
134
|
+
vm = Jsonnet::VM.new
|
135
|
+
vm.tla_var("var1", "foo")
|
136
|
+
result = vm.evaluate('function(var1) [var1, var1]')
|
137
|
+
assert_equal JSON.parse('["foo", "foo"]'), JSON.parse(result)
|
138
|
+
end
|
139
|
+
|
140
|
+
test "Jsonnet::VM#tla_var binds a top-level argument to a string value" do
|
141
|
+
vm = Jsonnet::VM.new
|
142
|
+
vm.tla_var("var1", "foo")
|
143
|
+
result = vm.evaluate('function(var1) [var1, var1]')
|
144
|
+
assert_equal JSON.parse('["foo", "foo"]'), JSON.parse(result)
|
145
|
+
end
|
146
|
+
|
147
|
+
test "Jsonnet::VM#tla_code binds a top-level argument to a code fragment" do
|
148
|
+
vm = Jsonnet::VM.new
|
149
|
+
vm.tla_code("var1", "{a:1}")
|
150
|
+
result = vm.evaluate('function(var1) [var1, var1]')
|
151
|
+
assert_equal JSON.parse(<<-EOS), JSON.parse(result)
|
152
|
+
[
|
153
|
+
{
|
154
|
+
"a": 1
|
155
|
+
},
|
156
|
+
{
|
157
|
+
"a": 1
|
158
|
+
}
|
159
|
+
]
|
160
|
+
EOS
|
161
|
+
end
|
162
|
+
|
120
163
|
test 'Jsonnet::VM#evaluate returns a JSON per filename on multi mode' do
|
121
164
|
vm = Jsonnet::VM.new
|
122
165
|
[
|
@@ -211,10 +254,6 @@ class TestVM < Test::Unit::TestCase
|
|
211
254
|
Jsonnet::VM.new.max_trace = 1
|
212
255
|
end
|
213
256
|
|
214
|
-
test "Jsonnet::VM responds to debug_ast=" do
|
215
|
-
Jsonnet::VM.new.debug_ast = true
|
216
|
-
end
|
217
|
-
|
218
257
|
test "Jsonnet::VM#string_output lets the VM output a raw string" do
|
219
258
|
vm = Jsonnet::VM.new
|
220
259
|
vm.string_output = true
|
@@ -229,7 +268,7 @@ class TestVM < Test::Unit::TestCase
|
|
229
268
|
case [base, rel]
|
230
269
|
when ['/path/to/base/', 'imported1.jsonnet']
|
231
270
|
return <<-EOS, '/path/to/imported1/imported1.jsonnet'
|
232
|
-
import "imported2.jsonnet" {
|
271
|
+
(import "imported2.jsonnet") + {
|
233
272
|
b: 2,
|
234
273
|
}
|
235
274
|
EOS
|
@@ -242,7 +281,7 @@ class TestVM < Test::Unit::TestCase
|
|
242
281
|
end
|
243
282
|
}
|
244
283
|
result = vm.evaluate(<<-EOS, filename: "/path/to/base/example.jsonnet")
|
245
|
-
import "imported1.jsonnet" { c: 3 }
|
284
|
+
(import "imported1.jsonnet") + { c: 3 }
|
246
285
|
EOS
|
247
286
|
|
248
287
|
expected = {"a" => 1, "b" => 2, "c" => 3}
|
@@ -255,12 +294,276 @@ class TestVM < Test::Unit::TestCase
|
|
255
294
|
vm.import_callback = ->(base, rel) { called = true; raise }
|
256
295
|
assert_raise(Jsonnet::EvaluationError) {
|
257
296
|
vm.evaluate(<<-EOS)
|
258
|
-
import "a.jsonnet" {}
|
297
|
+
(import "a.jsonnet") + {}
|
259
298
|
EOS
|
260
299
|
}
|
261
300
|
assert_true called
|
262
301
|
end
|
263
302
|
|
303
|
+
test "Jsonnet::VM#handle_import treats global escapes as define_method does" do
|
304
|
+
num_eval = 0
|
305
|
+
begin
|
306
|
+
bodies = [
|
307
|
+
proc {|rel, base| return 'null', '/x.libsonnet' },
|
308
|
+
lambda {|rel, base| return 'null', '/x.libsonnet' },
|
309
|
+
proc {|rel, base| next 'null', '/x.libsonnet' },
|
310
|
+
lambda {|rel, base| next 'null', '/x.libsonnet' },
|
311
|
+
proc {|rel, base| break 'null', '/x.libsonnet' },
|
312
|
+
lambda {|rel, base| break 'null', '/x.libsonnet' },
|
313
|
+
]
|
314
|
+
bodies.each do |prc|
|
315
|
+
vm = Jsonnet::VM.new
|
316
|
+
vm.handle_import(&prc)
|
317
|
+
|
318
|
+
result = vm.evaluate('import "a.jsonnet"')
|
319
|
+
assert_nil JSON.load(result)
|
320
|
+
|
321
|
+
num_eval += 1
|
322
|
+
end
|
323
|
+
ensure
|
324
|
+
assert_equal bodies.size, num_eval
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
test "Jsonnet::VM#handle_import is safe on throw" do
|
329
|
+
[
|
330
|
+
proc {|rel, base| throw :dummy },
|
331
|
+
lambda {|rel, base| throw :dummy },
|
332
|
+
].each do |prc|
|
333
|
+
vm = Jsonnet::VM.new
|
334
|
+
vm.handle_import(&prc)
|
335
|
+
|
336
|
+
catch(:dummy) {
|
337
|
+
vm.evaluate('import "a.jsonnet"')
|
338
|
+
flunk "never reach here"
|
339
|
+
}
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
test "Jsonnet::VM#jpath_add adds a library search path" do
|
344
|
+
vm = Jsonnet::VM.new
|
345
|
+
snippet = "(import 'jpath.libsonnet') {b: 2}"
|
346
|
+
assert_raise(Jsonnet::EvaluationError) {
|
347
|
+
vm.evaluate(snippet)
|
348
|
+
}
|
349
|
+
|
350
|
+
vm.jpath_add(File.join(__dir__, 'fixtures'))
|
351
|
+
result = vm.evaluate(snippet)
|
352
|
+
assert_equal JSON.parse(<<-EOS), JSON.parse(result)
|
353
|
+
{
|
354
|
+
"a": 1,
|
355
|
+
"b": 2
|
356
|
+
}
|
357
|
+
EOS
|
358
|
+
end
|
359
|
+
|
360
|
+
test "Jsonnet::VM#define_function adds a new native extension" do
|
361
|
+
vm = Jsonnet::VM.new
|
362
|
+
called = false
|
363
|
+
|
364
|
+
vm.define_function("myPow") do |x, y|
|
365
|
+
called = true
|
366
|
+
x ** y
|
367
|
+
end
|
368
|
+
|
369
|
+
result = vm.evaluate("std.native('myPow')(3, 4)")
|
370
|
+
assert_equal 3**4, JSON.load(result)
|
371
|
+
assert_true called
|
372
|
+
end
|
373
|
+
|
374
|
+
test "Jsonnet::VM#define_function passes various types of arguments" do
|
375
|
+
[
|
376
|
+
[%q(null), nil],
|
377
|
+
[%q("abc"), "abc"],
|
378
|
+
[%q(1), 1.0],
|
379
|
+
[%q(1.25), 1.25],
|
380
|
+
[%q(true), true],
|
381
|
+
[%q(false), false],
|
382
|
+
].each do |expr, value|
|
383
|
+
vm = Jsonnet::VM.new
|
384
|
+
vm.define_function("myFunc") do |x|
|
385
|
+
assert_equal value, x
|
386
|
+
next nil
|
387
|
+
end
|
388
|
+
vm.evaluate("std.native('myFunc')(#{expr})")
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
test "Jsonnet::VM#define_function returns various types of values" do
|
393
|
+
[
|
394
|
+
[nil, nil],
|
395
|
+
["abc", "abc"],
|
396
|
+
[1, 1.0],
|
397
|
+
[1.25, 1.25],
|
398
|
+
[true, true],
|
399
|
+
[false, false],
|
400
|
+
].each do |retval, expected|
|
401
|
+
vm = Jsonnet::VM.new
|
402
|
+
vm.define_function("myFunc") { retval }
|
403
|
+
|
404
|
+
result = vm.evaluate("std.native('myFunc')()")
|
405
|
+
assert_equal expected, JSON.load(result)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
test "Jsonnet::VM#define_function translates an exception in a native function into an error" do
|
410
|
+
vm = Jsonnet::VM.new
|
411
|
+
vm.define_function("myFunc") do |x|
|
412
|
+
raise "something wrong"
|
413
|
+
end
|
414
|
+
assert_raise(Jsonnet::EvaluationError) {
|
415
|
+
vm.evaluate("std.native('myFunc')(1)")
|
416
|
+
}
|
417
|
+
end
|
418
|
+
|
419
|
+
test "Jsonnet::VM#define_function let the function return a compound object" do
|
420
|
+
vm = Jsonnet::VM.new
|
421
|
+
vm.define_function("myCompound") do |x, y|
|
422
|
+
{
|
423
|
+
x => y,
|
424
|
+
y => [x, y, y, x],
|
425
|
+
}
|
426
|
+
end
|
427
|
+
|
428
|
+
result = vm.evaluate("std.native('myCompound')('abc', 'def')")
|
429
|
+
assert_equal JSON.parse(<<-EOS), JSON.parse(result)
|
430
|
+
{
|
431
|
+
"abc": "def",
|
432
|
+
"def": ["abc", "def", "def", "abc"]
|
433
|
+
}
|
434
|
+
EOS
|
435
|
+
end
|
436
|
+
|
437
|
+
test "Jsonnet::VM#define_function treats global escapes as define_method does" do
|
438
|
+
num_eval = 0
|
439
|
+
begin
|
440
|
+
bodies = [
|
441
|
+
proc {|x| return x },
|
442
|
+
lambda {|x| return x },
|
443
|
+
proc {|x| next x },
|
444
|
+
lambda {|x| next x },
|
445
|
+
proc {|x| break x },
|
446
|
+
lambda {|x| break x },
|
447
|
+
]
|
448
|
+
bodies.each do |prc|
|
449
|
+
vm = Jsonnet::VM.new
|
450
|
+
vm.define_function(:myFunc, prc)
|
451
|
+
|
452
|
+
result = vm.evaluate('std.native("myFunc")(1.25) + 0.25')
|
453
|
+
assert_equal 1.25 + 0.25, JSON.load(result)
|
454
|
+
|
455
|
+
num_eval += 1
|
456
|
+
end
|
457
|
+
ensure
|
458
|
+
assert_equal bodies.size, num_eval
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
test "Jsonnet::VM#define_function is safe on throw" do
|
463
|
+
[
|
464
|
+
proc {|x| throw :dummy },
|
465
|
+
lambda {|x| throw :dummy },
|
466
|
+
].each do |prc|
|
467
|
+
vm = Jsonnet::VM.new
|
468
|
+
vm.define_function(:myFunc, prc)
|
469
|
+
|
470
|
+
catch(:dummy) {
|
471
|
+
vm.evaluate('std.native("myFunc")(1.234)')
|
472
|
+
flunk "never reach here"
|
473
|
+
}
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
test "Jsonnet::VM#format_file formats Jsonnet file" do
|
478
|
+
vm = Jsonnet::VM.new
|
479
|
+
vm.fmt_indent = 4
|
480
|
+
with_example_file(%<
|
481
|
+
local myvar = 1;
|
482
|
+
{
|
483
|
+
"foo": myvar
|
484
|
+
}
|
485
|
+
>) {|fname|
|
486
|
+
result = vm.format_file(fname)
|
487
|
+
assert_equal <<-EOS, result
|
488
|
+
local myvar = 1;
|
489
|
+
{
|
490
|
+
foo: myvar,
|
491
|
+
}
|
492
|
+
EOS
|
493
|
+
}
|
494
|
+
end
|
495
|
+
|
496
|
+
test "Jsonnet::VM#format formats Jsonnet snippet" do
|
497
|
+
vm = Jsonnet::VM.new
|
498
|
+
vm.fmt_string = 'd'
|
499
|
+
result = vm.format(<<-EOS)
|
500
|
+
local myvar = 'myvar';
|
501
|
+
{
|
502
|
+
foo: [myvar,myvar]
|
503
|
+
}
|
504
|
+
EOS
|
505
|
+
assert_equal <<-EOS, result
|
506
|
+
local myvar = "myvar";
|
507
|
+
{
|
508
|
+
foo: [myvar, myvar],
|
509
|
+
}
|
510
|
+
EOS
|
511
|
+
end
|
512
|
+
|
513
|
+
test "Jsonnet::VM#fmt_string only accepts 'd', 's', or 'l'" do
|
514
|
+
vm = Jsonnet::VM.new
|
515
|
+
vm.fmt_string = Jsonnet::STRING_STYLE_DOUBLE
|
516
|
+
vm.fmt_string = Jsonnet::STRING_STYLE_SINGLE
|
517
|
+
vm.fmt_string = Jsonnet::STRING_STYLE_LEAVE
|
518
|
+
assert_raise(ArgumentError) do
|
519
|
+
vm.fmt_string = ''
|
520
|
+
end
|
521
|
+
assert_raise(ArgumentError) do
|
522
|
+
vm.fmt_string = 'a'
|
523
|
+
end
|
524
|
+
assert_raise(ArgumentError) do
|
525
|
+
vm.fmt_string = 'ds'
|
526
|
+
end
|
527
|
+
assert_raise(TypeError) do
|
528
|
+
vm.fmt_string = 0
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
test "Jsonnet::VM#fmt_comment only accepts 'h', 's', or 'l'" do
|
533
|
+
vm = Jsonnet::VM.new
|
534
|
+
vm.fmt_comment = Jsonnet::COMMENT_STYLE_HASH
|
535
|
+
vm.fmt_comment = Jsonnet::COMMENT_STYLE_SLASH
|
536
|
+
vm.fmt_comment = Jsonnet::COMMENT_STYLE_LEAVE
|
537
|
+
assert_raise(ArgumentError) do
|
538
|
+
vm.fmt_comment = ''
|
539
|
+
end
|
540
|
+
assert_raise(ArgumentError) do
|
541
|
+
vm.fmt_comment = 'a'
|
542
|
+
end
|
543
|
+
assert_raise(ArgumentError) do
|
544
|
+
vm.fmt_comment = 'hs'
|
545
|
+
end
|
546
|
+
assert_raise(TypeError) do
|
547
|
+
vm.fmt_comment = 0
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
test "Jsonnet::VM#fmt_file raises FormatError on error" do
|
552
|
+
vm = Jsonnet::VM.new
|
553
|
+
with_example_file('{foo: }') do |fname|
|
554
|
+
assert_raise(Jsonnet::FormatError) do
|
555
|
+
vm.format_file(fname)
|
556
|
+
end
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
test "Jsonnet::VM#fmt_snippet raises FormatError on error" do
|
561
|
+
vm = Jsonnet::VM.new
|
562
|
+
assert_raise(Jsonnet::FormatError) do
|
563
|
+
vm.format('{foo: }')
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
264
567
|
private
|
265
568
|
def with_example_file(content)
|
266
569
|
Tempfile.open("example.jsonnet") {|f|
|