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