Implemented jsonnet_import_callback c-binding (#330)

Implemented jsonnet_import_callback c-binding
This commit is contained in:
Alexander Petrov 2019-10-14 19:49:24 +01:00 committed by Stanisław Barzowski
parent 42cb19ef24
commit acf0e5cfbf
4 changed files with 127 additions and 1 deletions

View File

@ -2,6 +2,7 @@
import unittest import unittest
import ctypes import ctypes
import re import re
import os
lib = ctypes.CDLL('../c-bindings/libgojsonnet.so') lib = ctypes.CDLL('../c-bindings/libgojsonnet.so')
@ -80,6 +81,23 @@ lib.jsonnet_native_callback.argtypes = [
] ]
lib.jsonnet_native_callback.restype = None 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 # json declaration
lib.jsonnet_json_make_string.argtypes = [ lib.jsonnet_json_make_string.argtypes = [
@ -206,6 +224,38 @@ def build_native(ctx, argv, success):
success[0] = ctypes.c_int(1) success[0] = ctypes.c_int(1)
return res 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): class TestJsonnetEvaluateBindings(unittest.TestCase):
def setUp(self): def setUp(self):
self.err = ctypes.c_int() self.err = ctypes.c_int()
@ -291,6 +341,13 @@ class TestJsonnetEvaluateBindings(unittest.TestCase):
free_buffer(self.vm, res) 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): def tearDown(self):
lib.jsonnet_destroy(self.vm) lib.jsonnet_destroy(self.vm)

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"unsafe" "unsafe"
"github.com/google/go-jsonnet" "github.com/google/go-jsonnet"
@ -25,6 +26,33 @@ type jsonValue struct {
owned []*C.struct_JsonnetJsonValue 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 handles = handlesTable{}
var versionString *C.char 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) 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 //export jsonnet_json_extract_string
func jsonnet_json_extract_string(vmRef *C.struct_JsonnetVm, json *C.struct_JsonnetJsonValue) *C.char { func jsonnet_json_extract_string(vmRef *C.struct_JsonnetVm, json *C.struct_JsonnetJsonValue) *C.char {
v := getJSONValue(json) v := getJSONValue(json)

View File

@ -24,3 +24,18 @@ struct JsonnetJsonValue* jsonnet_internal_execute_native(JsonnetNativeCallback *
void *ctx, void *ctx,
const struct JsonnetJsonValue *const *argv, const struct JsonnetJsonValue *const *argv,
int *success); 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);

View File

@ -34,6 +34,22 @@ struct JsonnetJsonValue* jsonnet_internal_execute_native(JsonnetNativeCallback *
return (cb)(ctx, argv, success); 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() { inline static void todo() {
fputs("TODO, NOT IMPLEMENTED YET\n", stderr); fputs("TODO, NOT IMPLEMENTED YET\n", stderr);
abort(); abort();