diff --git a/c-bindings-tests/compat_test.py b/c-bindings-tests/compat_test.py index e88333b..880fa20 100755 --- a/c-bindings-tests/compat_test.py +++ b/c-bindings-tests/compat_test.py @@ -2,6 +2,7 @@ import unittest import ctypes import re +import os lib = ctypes.CDLL('../c-bindings/libgojsonnet.so') @@ -80,6 +81,23 @@ lib.jsonnet_native_callback.argtypes = [ ] lib.jsonnet_native_callback.restype = None +IMPORT_CALLBACK = ctypes.CFUNCTYPE( + ctypes.c_char_p, + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_char), + ctypes.POINTER(ctypes.c_char), + # we use *int instead of **char to pass the real C allocated pointer, that we have to free + ctypes.POINTER(ctypes.c_uint64), + ctypes.POINTER(ctypes.c_int) +) + +lib.jsonnet_import_callback.argtypes = [ + ctypes.c_void_p, + IMPORT_CALLBACK, + ctypes.c_void_p, +] +lib.jsonnet_import_callback.restype = None + # json declaration lib.jsonnet_json_make_string.argtypes = [ @@ -206,6 +224,38 @@ def build_native(ctx, argv, success): success[0] = ctypes.c_int(1) return res +@IMPORT_CALLBACK +def import_callback(ctx, dir, rel, found_here, success): + full_path, content = jsonnet_try_path(b"jsonnet_import_test/", to_bytes(rel)) + + bcontent = content.encode() + dst = lib.jsonnet_realloc(ctx, None, len(bcontent) + 1) + ctypes.memmove(ctypes.addressof(dst.contents), bcontent, len(bcontent) + 1) + + fdst = lib.jsonnet_realloc(ctx, None, len(full_path) + 1) + ctypes.memmove(ctypes.addressof(fdst.contents), full_path, len(full_path) + 1) + found_here[0] = ctypes.addressof(fdst.contents) + + success[0] = ctypes.c_int(1) + + return ctypes.addressof(dst.contents) + +# Returns content if worked, None if file not found, or throws an exception +def jsonnet_try_path(dir, rel): + if not rel: + raise RuntimeError('Got invalid filename (empty string).') + if rel[0] == '/': + full_path = rel + else: + full_path = dir + rel + if full_path[-1] == '/': + raise RuntimeError('Attempted to import a directory') + + if not os.path.isfile(full_path): + return full_path, None + with open(full_path) as f: + return full_path, f.read() + class TestJsonnetEvaluateBindings(unittest.TestCase): def setUp(self): self.err = ctypes.c_int() @@ -291,6 +341,13 @@ class TestJsonnetEvaluateBindings(unittest.TestCase): free_buffer(self.vm, res) + def test_jsonnet_import_callback(self): + lib.jsonnet_import_callback(self.vm, import_callback, self.vm) + + res = lib.jsonnet_evaluate_snippet(self.vm, b"jsonnet_import_callback", b"""import 'foo.jsonnet'""", self.err_ref) + self.assertEqual(b'42\n', to_bytes(res)) + free_buffer(self.vm, res) + def tearDown(self): lib.jsonnet_destroy(self.vm) diff --git a/c-bindings/c-bindings.go b/c-bindings/c-bindings.go index 76348f1..00fb277 100644 --- a/c-bindings/c-bindings.go +++ b/c-bindings/c-bindings.go @@ -4,6 +4,7 @@ import ( "fmt" "io/ioutil" "os" + "path" "unsafe" "github.com/google/go-jsonnet" @@ -25,6 +26,33 @@ type jsonValue struct { owned []*C.struct_JsonnetJsonValue } +type importer struct { + cb *C.JsonnetImportCallback + ctx unsafe.Pointer +} + +// Import fetches data from a given path by using c.JsonnetImportCallback +func (i *importer) Import(importedFrom, importedPath string) (contents jsonnet.Contents, foundAt string, err error) { + var ( + success = C.int(0) + dir, _ = path.Split(importedFrom) + foundHereC *C.char + ) + + resultC := C.jsonnet_internal_execute_import(i.cb, i.ctx, C.CString(dir), C.CString(importedPath), &foundHereC, &success) + result := C.GoString(resultC) + C.jsonnet_internal_free_string(resultC) + + foundHere := C.GoString(foundHereC) + C.jsonnet_internal_free_string(foundHereC) + + if success != 1 { + return jsonnet.Contents{}, "", fmt.Errorf("failed to execute import callback, code: %d", success) + } + + return jsonnet.MakeContents(result), foundHere, nil +} + var handles = handlesTable{} var versionString *C.char @@ -219,6 +247,16 @@ func jsonnet_native_callback(vmRef *C.struct_JsonnetVm, name *C.char, cb *C.Json vm.NativeFunction(f) } +//export jsonnet_import_callback +func jsonnet_import_callback(vmRef *C.struct_JsonnetVm, cb *C.JsonnetImportCallback, ctx unsafe.Pointer) { + vm := getVM(vmRef) + + vm.Importer(&importer{ + ctx: ctx, + cb: cb, + }) +} + //export jsonnet_json_extract_string func jsonnet_json_extract_string(vmRef *C.struct_JsonnetVm, json *C.struct_JsonnetJsonValue) *C.char { v := getJSONValue(json) diff --git a/c-bindings/internal.h b/c-bindings/internal.h index 897931b..d8c60ac 100644 --- a/c-bindings/internal.h +++ b/c-bindings/internal.h @@ -23,4 +23,19 @@ typedef struct JsonnetJsonValue *JsonnetNativeCallback(void *ctx, struct JsonnetJsonValue* jsonnet_internal_execute_native(JsonnetNativeCallback *cb, void *ctx, const struct JsonnetJsonValue *const *argv, - int *success); \ No newline at end of file + int *success); + +typedef char *JsonnetImportCallback(void *ctx, + const char *base, + const char *rel, + char **found_here, + int *success); + +char* jsonnet_internal_execute_import(JsonnetImportCallback *cb, + void *ctx, + const char *base, + const char *rel, + char **found_here, + int *success); + +void jsonnet_internal_free_string(char *str); diff --git a/c-bindings/libjsonnet.cpp b/c-bindings/libjsonnet.cpp index ed7af42..1669ce2 100644 --- a/c-bindings/libjsonnet.cpp +++ b/c-bindings/libjsonnet.cpp @@ -34,6 +34,22 @@ struct JsonnetJsonValue* jsonnet_internal_execute_native(JsonnetNativeCallback * return (cb)(ctx, argv, success); } +char* jsonnet_internal_execute_import(JsonnetImportCallback *cb, + void *ctx, + const char *base, + const char *rel, + char **found_here, + int *success) +{ + return (cb)(ctx, base, rel, found_here, success); +} + +void jsonnet_internal_free_string(char *str) { + if (str != nullptr) { + ::free(str); + } +} + inline static void todo() { fputs("TODO, NOT IMPLEMENTED YET\n", stderr); abort();