Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 164 additions & 10 deletions ext/jsonnet/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ static VALUE cVM;
* Raised on evaluation errors in a Jsonnet VM.
*/
static VALUE eEvaluationError;
static VALUE eFormatError;

static void raise_eval_error(struct JsonnetVm *vm, char *msg, rb_encoding *enc);
static void raise_format_error(struct JsonnetVm *vm, char *msg, rb_encoding *enc);
static VALUE str_new_json(struct JsonnetVm *vm, char *json, rb_encoding *enc);
static VALUE fileset_new(struct JsonnetVm *vm, char *buf, rb_encoding *enc);

Expand Down Expand Up @@ -255,13 +257,137 @@ vm_set_max_trace(VALUE self, VALUE val)
return Qnil;
}

static VALUE
vm_set_fmt_indent(VALUE self, VALUE val)
{
struct jsonnet_vm_wrap *vm = rubyjsonnet_obj_to_vm(self);
jsonnet_fmt_indent(vm->vm, NUM2INT(val));
return val;
}

static VALUE
vm_set_fmt_max_blank_lines(VALUE self, VALUE val)
{
struct jsonnet_vm_wrap *vm = rubyjsonnet_obj_to_vm(self);
jsonnet_fmt_max_blank_lines(vm->vm, NUM2INT(val));
return val;
}

static VALUE
vm_set_fmt_string(VALUE self, VALUE str)
{
const char *ptr;
struct jsonnet_vm_wrap *vm = rubyjsonnet_obj_to_vm(self);
StringValue(str);
if (RSTRING_LEN(str) != 1) {
rb_raise(rb_eArgError, "fmt_string must have a length of 1");
}
ptr = RSTRING_PTR(str);
switch (*ptr) {
case 'd':
case 's':
case 'l':
jsonnet_fmt_string(vm->vm, *ptr);
return str;
default:
rb_raise(rb_eArgError, "fmt_string only accepts 'd', 's', or 'l'");
}
}

static VALUE
vm_set_fmt_comment(VALUE self, VALUE str)
{
const char *ptr;
struct jsonnet_vm_wrap *vm = rubyjsonnet_obj_to_vm(self);
StringValue(str);
if (RSTRING_LEN(str) != 1) {
rb_raise(rb_eArgError, "fmt_comment must have a length of 1");
}
ptr = RSTRING_PTR(str);
switch (*ptr) {
case 'h':
case 's':
case 'l':
jsonnet_fmt_comment(vm->vm, *ptr);
return str;
default:
rb_raise(rb_eArgError, "fmt_comment only accepts 'h', 's', or 'l'");
}
}

static VALUE
vm_set_fmt_pad_arrays(VALUE self, VALUE val)
{
struct jsonnet_vm_wrap *vm = rubyjsonnet_obj_to_vm(self);
jsonnet_fmt_pad_objects(vm->vm, RTEST(val) ? 1 : 0);
return val;
}

static VALUE
vm_set_fmt_pad_objects(VALUE self, VALUE val)
{
struct jsonnet_vm_wrap *vm = rubyjsonnet_obj_to_vm(self);
jsonnet_fmt_pad_objects(vm->vm, RTEST(val) ? 1 : 0);
return val;
}

static VALUE
vm_set_fmt_pretty_field_names(VALUE self, VALUE val)
{
struct jsonnet_vm_wrap *vm = rubyjsonnet_obj_to_vm(self);
jsonnet_fmt_pretty_field_names(vm->vm, RTEST(val) ? 1 : 0);
return val;
}

static VALUE
vm_set_fmt_sort_imports(VALUE self, VALUE val)
{
struct jsonnet_vm_wrap *vm = rubyjsonnet_obj_to_vm(self);
jsonnet_fmt_sort_imports(vm->vm, RTEST(val) ? 1 : 0);
return val;
}

static VALUE
vm_fmt_file(VALUE self, VALUE fname, VALUE encoding)
{
int error;
char *result;
rb_encoding *const enc = rb_to_encoding(encoding);
struct jsonnet_vm_wrap *vm = rubyjsonnet_obj_to_vm(self);

FilePathValue(fname);
result = jsonnet_fmt_file(vm->vm, StringValueCStr(fname), &error);
if (error) {
raise_format_error(vm->vm, result, rb_enc_get(fname));
}
return str_new_json(vm->vm, result, enc);
}

static VALUE
vm_fmt_snippet(VALUE self, VALUE snippet, VALUE fname)
{
int error;
char *result;
struct jsonnet_vm_wrap *vm = rubyjsonnet_obj_to_vm(self);

rb_encoding *enc = rubyjsonnet_assert_asciicompat(StringValue(snippet));
FilePathValue(fname);
result = jsonnet_fmt_snippet(vm->vm, StringValueCStr(fname), StringValueCStr(snippet), &error);
if (error) {
raise_format_error(vm->vm, result, rb_enc_get(fname));
}
return str_new_json(vm->vm, result, enc);
}

void
rubyjsonnet_init_vm(VALUE mJsonnet)
{
cVM = rb_define_class_under(mJsonnet, "VM", rb_cData);
rb_define_singleton_method(cVM, "new", vm_s_new, -1);
rb_define_private_method(cVM, "eval_file", vm_evaluate_file, 3);
rb_define_private_method(cVM, "eval_snippet", vm_evaluate, 3);
rb_define_private_method(cVM, "fmt_file", vm_fmt_file, 2);
rb_define_private_method(cVM, "fmt_snippet", vm_fmt_snippet, 2);
rb_define_method(cVM, "ext_var", vm_ext_var, 2);
rb_define_method(cVM, "ext_code", vm_ext_code, 2);
rb_define_method(cVM, "tla_var", vm_tla_var, 2);
Expand All @@ -272,22 +398,30 @@ rubyjsonnet_init_vm(VALUE mJsonnet)
rb_define_method(cVM, "gc_growth_trigger=", vm_set_gc_growth_trigger, 1);
rb_define_method(cVM, "string_output=", vm_set_string_output, 1);
rb_define_method(cVM, "max_trace=", vm_set_max_trace, 1);
rb_define_method(cVM, "fmt_indent=", vm_set_fmt_indent, 1);
rb_define_method(cVM, "fmt_max_blank_lines=", vm_set_fmt_max_blank_lines, 1);
rb_define_method(cVM, "fmt_string=", vm_set_fmt_string, 1);
rb_define_method(cVM, "fmt_comment=", vm_set_fmt_comment, 1);
rb_define_method(cVM, "fmt_pad_arrays=", vm_set_fmt_pad_arrays, 1);
rb_define_method(cVM, "fmt_pad_objects=", vm_set_fmt_pad_objects, 1);
rb_define_method(cVM, "fmt_pretty_field_names=", vm_set_fmt_pretty_field_names, 1);
rb_define_method(cVM, "fmt_sort_imports=", vm_set_fmt_sort_imports, 1);

rb_define_const(mJsonnet, "STRING_STYLE_DOUBLE", rb_str_new_cstr("d"));
rb_define_const(mJsonnet, "STRING_STYLE_SINGLE", rb_str_new_cstr("s"));
rb_define_const(mJsonnet, "STRING_STYLE_LEAVE", rb_str_new_cstr("l"));
rb_define_const(mJsonnet, "COMMENT_STYLE_HASH", rb_str_new_cstr("h"));
rb_define_const(mJsonnet, "COMMENT_STYLE_SLASH", rb_str_new_cstr("s"));
rb_define_const(mJsonnet, "COMMENT_STYLE_LEAVE", rb_str_new_cstr("l"));

rubyjsonnet_init_callbacks(cVM);

eEvaluationError = rb_define_class_under(mJsonnet, "EvaluationError", rb_eRuntimeError);
eFormatError = rb_define_class_under(mJsonnet, "FormatError", rb_eRuntimeError);
}

/**
* raises an EvaluationError whose message is \c msg.
* @param[in] vm a JsonnetVM
* @param[in] msg must be a NUL-terminated string returned by \c vm.
* @return never returns
* @throw EvaluationError
* @sa rescue_callback
*/
static void
raise_eval_error(struct JsonnetVm *vm, char *msg, rb_encoding *enc)
raise_error(VALUE exception_class, struct JsonnetVm *vm, char *msg, rb_encoding *enc)
{
VALUE ex;
const int state = rubyjsonnet_jump_tag(msg);
Expand All @@ -300,11 +434,31 @@ raise_eval_error(struct JsonnetVm *vm, char *msg, rb_encoding *enc)
rb_jump_tag(state);
}

ex = rb_exc_new3(eEvaluationError, rb_enc_str_new_cstr(msg, enc));
ex = rb_exc_new3(exception_class, rb_enc_str_new_cstr(msg, enc));
jsonnet_realloc(vm, msg, 0);
rb_exc_raise(ex);
}

/**
* raises an EvaluationError whose message is \c msg.
* @param[in] vm a JsonnetVM
* @param[in] msg must be a NUL-terminated string returned by \c vm.
* @return never returns
* @throw EvaluationError
* @sa rescue_callback
*/
static void
raise_eval_error(struct JsonnetVm *vm, char *msg, rb_encoding *enc)
{
raise_error(eEvaluationError, vm, msg, enc);
}

static void
raise_format_error(struct JsonnetVm *vm, char *msg, rb_encoding *enc)
{
raise_error(eFormatError, vm, msg, enc);
}

/**
* Returns a String whose contents is equal to \c json.
* It automatically frees \c json just after constructing the return value.
Expand Down
22 changes: 22 additions & 0 deletions lib/jsonnet/vm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,28 @@ def evaluate_file(filename, encoding: Encoding.default_external, multi: false)
eval_file(filename, encoding, multi)
end

##
# Format Jsonnet file.
#
# @param [String] filename filename of a Jsonnet source file.
# @return [String] a formatted Jsonnet representation
# @raise [FormatError] raised when the formatting results an error.
def format_file(filename, encoding: Encoding.default_external)
fmt_file(filename, encoding)
end

##
# Format Jsonnet snippet.
#
# @param [String] jsonnet Jsonnet source string. Must be encoded in ASCII-compatible encoding.
# @param [String] filename filename of the source. Used in stacktrace.
# @return [String] a formatted Jsonnet representation
# @raise [FormatError] raised when the formatting results an error.
# @raise [UnsupportedEncodingError] raised when the encoding of jsonnt is not ASCII-compatible.
def format(jsonnet, filename: "(jsonnet)")
fmt_snippet(jsonnet, filename)
end

##
# Lets the given block handle "import" expression of Jsonnet.
# @yieldparam [String] base base path to resolve "rel" from.
Expand Down
90 changes: 90 additions & 0 deletions test/test_vm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,96 @@ class TestVM < Test::Unit::TestCase
end
end

test "Jsonnet::VM#format_file formats Jsonnet file" do
vm = Jsonnet::VM.new
vm.fmt_indent = 4
with_example_file(%<
local myvar = 1;
{
"foo": myvar
}
>) {|fname|
result = vm.format_file(fname)
assert_equal <<-EOS, result
local myvar = 1;
{
foo: myvar,
}
EOS
}
end

test "Jsonnet::VM#format formats Jsonnet snippet" do
vm = Jsonnet::VM.new
vm.fmt_string = 'd'
result = vm.format(<<-EOS)
local myvar = 'myvar';
{
foo: [myvar,myvar]
}
EOS
assert_equal <<-EOS, result
local myvar = "myvar";
{
foo: [myvar, myvar],
}
EOS
end

test "Jsonnet::VM#fmt_string only accepts 'd', 's', or 'l'" do
vm = Jsonnet::VM.new
vm.fmt_string = Jsonnet::STRING_STYLE_DOUBLE
vm.fmt_string = Jsonnet::STRING_STYLE_SINGLE
vm.fmt_string = Jsonnet::STRING_STYLE_LEAVE
assert_raise(ArgumentError) do
vm.fmt_string = ''
end
assert_raise(ArgumentError) do
vm.fmt_string = 'a'
end
assert_raise(ArgumentError) do
vm.fmt_string = 'ds'
end
assert_raise(TypeError) do
vm.fmt_string = 0
end
end

test "Jsonnet::VM#fmt_comment only accepts 'h', 's', or 'l'" do
vm = Jsonnet::VM.new
vm.fmt_comment = Jsonnet::COMMENT_STYLE_HASH
vm.fmt_comment = Jsonnet::COMMENT_STYLE_SLASH
vm.fmt_comment = Jsonnet::COMMENT_STYLE_LEAVE
assert_raise(ArgumentError) do
vm.fmt_comment = ''
end
assert_raise(ArgumentError) do
vm.fmt_comment = 'a'
end
assert_raise(ArgumentError) do
vm.fmt_comment = 'hs'
end
assert_raise(TypeError) do
vm.fmt_comment = 0
end
end

test "Jsonnet::VM#fmt_file raises FormatError on error" do
vm = Jsonnet::VM.new
with_example_file('{foo: }') do |fname|
assert_raise(Jsonnet::FormatError) do
vm.format_file(fname)
end
end
end

test "Jsonnet::VM#fmt_snippet raises FormatError on error" do
vm = Jsonnet::VM.new
assert_raise(Jsonnet::FormatError) do
vm.format('{foo: }')
end
end

private
def with_example_file(content)
Tempfile.open("example.jsonnet") {|f|
Expand Down