mirror of
https://github.com/google/go-jsonnet.git
synced 2025-08-08 07:17:12 +02:00
We used to treat dummy paths like <stdin>, <std>, <extvar> as real import locations, which causes obvious problem for importers. After this change we consistently pass "" (an empty string) as location for non-imported paths. We exposed new functions to properly handle all paths. The first function family, EvaluateFile* which allow evaluating a Jsonnet program at a specified importable path. It should be used in most cases. The second function family, EvaluateAnonymousSnippet* allows evaluating a snippet without an importable path. It should be used for situations like the code passed as a commandline argument, stdin, etc. The old function family, EvaluateSnippet* is now deprecated, but works the same as before, i.e. the passed filenames are treated as imported paths. Changes are required to custom importers to make sure they satisfy the refined contract. Fixes #329.
455 lines
11 KiB
Go
455 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"unsafe"
|
|
|
|
"github.com/google/go-jsonnet"
|
|
"github.com/google/go-jsonnet/ast"
|
|
|
|
// #cgo CXXFLAGS: -std=c++11 -Wall -I../cpp-jsonnet/include
|
|
// #include "internal.h"
|
|
"C"
|
|
)
|
|
import "errors"
|
|
|
|
type vm struct {
|
|
*jsonnet.VM
|
|
importer *jsonnet.FileImporter
|
|
}
|
|
|
|
type jsonValue struct {
|
|
val interface{}
|
|
// these objects are exclusively owned by this jsonValue
|
|
owned []*C.struct_JsonnetJsonValue
|
|
}
|
|
|
|
type importer struct {
|
|
cb *C.JsonnetImportCallback
|
|
ctx unsafe.Pointer
|
|
|
|
// An additional level of cache which allows
|
|
// using this API as a drop-in replacement for
|
|
// the C++ version.
|
|
//
|
|
// Importer contract requires returning the same
|
|
// contents every time. This enforces that the same
|
|
// imported file will always have the same contents.
|
|
// This caching is not performed on the Go side normally,
|
|
// because in many cases the proper caching can only
|
|
// be performed within the importer. In particular,
|
|
// when multiple paths are tried, the presence and contents
|
|
// of each should be cached.
|
|
//
|
|
// In this case, the API requires us to take ownership
|
|
// of the provided string, so we the implementation needs
|
|
// to provide a new one each time, so we need to fake
|
|
// good behavior.
|
|
contentCache map[string]jsonnet.Contents
|
|
}
|
|
|
|
// 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
|
|
)
|
|
|
|
// TODO(sbarzowski) Consider supporting returning null for paths,
|
|
// which are already resolved. We cannot expect cross-language interface
|
|
// to let us easily return the same Go Contents. Instead, we can allow
|
|
// returning nothing (NULL), if they know that we have the contents
|
|
// cached anyway.
|
|
|
|
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{}, "", errors.New("importer error: " + result)
|
|
}
|
|
|
|
if _, isCached := i.contentCache[foundHere]; !isCached {
|
|
i.contentCache[foundHere] = jsonnet.MakeContents(result)
|
|
}
|
|
return i.contentCache[foundHere], foundHere, nil
|
|
}
|
|
|
|
var handles = newHandlesTable()
|
|
var versionString *C.char
|
|
|
|
//export jsonnet_version
|
|
func jsonnet_version() *C.char {
|
|
if versionString == nil {
|
|
version := jsonnet.Version() + " (go-jsonnet)"
|
|
versionString = C.CString(version)
|
|
}
|
|
return versionString
|
|
}
|
|
|
|
//export jsonnet_make
|
|
func jsonnet_make() *C.struct_JsonnetVm {
|
|
newVM := &vm{jsonnet.MakeVM(), &jsonnet.FileImporter{}}
|
|
newVM.Importer(newVM.importer)
|
|
|
|
id, err := handles.make(newVM)
|
|
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
return C.jsonnet_internal_make_vm_with_id(C.uintptr_t(id))
|
|
}
|
|
|
|
//export jsonnet_destroy
|
|
func jsonnet_destroy(vmRef *C.struct_JsonnetVm) {
|
|
if err := handles.free(uintptr(vmRef.id)); err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
C.jsonnet_internal_free_vm(vmRef)
|
|
}
|
|
|
|
func getVM(vmRef *C.struct_JsonnetVm) *vm {
|
|
ref, err := handles.get(uintptr(vmRef.id))
|
|
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
v, ok := ref.(*vm)
|
|
|
|
if !ok {
|
|
fmt.Fprintln(os.Stderr, "provided handle has a different type")
|
|
os.Exit(1)
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
func evaluateSnippet(vmRef *C.struct_JsonnetVm, filename string, code string, e *C.int) *C.char {
|
|
vm := getVM(vmRef)
|
|
// We still use a deprecated function to keep backwards compatible behavior.
|
|
//nolint:staticcheck
|
|
out, err := vm.EvaluateSnippet(filename, code)
|
|
var result *C.char
|
|
if err != nil {
|
|
*e = 1
|
|
result = C.CString(err.Error())
|
|
} else {
|
|
*e = 0
|
|
result = C.CString(out)
|
|
}
|
|
return result
|
|
}
|
|
|
|
//export jsonnet_evaluate_snippet
|
|
func jsonnet_evaluate_snippet(vmRef *C.struct_JsonnetVm, filename *C.char, code *C.char, e *C.int) *C.char {
|
|
f := C.GoString(filename)
|
|
s := C.GoString(code)
|
|
return evaluateSnippet(vmRef, f, s, e)
|
|
}
|
|
|
|
//export jsonnet_evaluate_file
|
|
func jsonnet_evaluate_file(vmRef *C.struct_JsonnetVm, filename *C.char, e *C.int) *C.char {
|
|
f := C.GoString(filename)
|
|
data, err := ioutil.ReadFile(f)
|
|
if err != nil {
|
|
*e = 1
|
|
// TODO(sbarzowski) make sure that it's ok allocation-wise
|
|
return C.CString(fmt.Sprintf("Failed to read input file: %s: %s", f, err.Error()))
|
|
}
|
|
return evaluateSnippet(vmRef, f, string(data), e)
|
|
}
|
|
|
|
//export jsonnet_max_stack
|
|
func jsonnet_max_stack(vmRef *C.struct_JsonnetVm, v C.uint) {
|
|
vm := getVM(vmRef)
|
|
vm.MaxStack = int(v) // potentially dangerous conversion
|
|
}
|
|
|
|
//export jsonnet_string_output
|
|
func jsonnet_string_output(vmRef *C.struct_JsonnetVm, v C.int) {
|
|
vm := getVM(vmRef)
|
|
vm.StringOutput = v != 0
|
|
}
|
|
|
|
//export jsonnet_max_trace
|
|
func jsonnet_max_trace(vmRef *C.struct_JsonnetVm, v C.uint) {
|
|
vm := getVM(vmRef)
|
|
vm.ErrorFormatter.SetMaxStackTraceSize(int(v)) // potentially dangerous conversion
|
|
}
|
|
|
|
//export jsonnet_jpath_add
|
|
func jsonnet_jpath_add(vmRef *C.struct_JsonnetVm, path *C.char) {
|
|
vm := getVM(vmRef)
|
|
vm.importer.JPaths = append(vm.importer.JPaths, C.GoString(path))
|
|
}
|
|
|
|
//export jsonnet_ext_var
|
|
func jsonnet_ext_var(vmRef *C.struct_JsonnetVm, key, value *C.char) {
|
|
vm := getVM(vmRef)
|
|
vm.ExtVar(C.GoString(key), C.GoString(value))
|
|
}
|
|
|
|
//export jsonnet_ext_code
|
|
func jsonnet_ext_code(vmRef *C.struct_JsonnetVm, key, value *C.char) {
|
|
vm := getVM(vmRef)
|
|
vm.ExtCode(C.GoString(key), C.GoString(value))
|
|
}
|
|
|
|
//export jsonnet_tla_var
|
|
func jsonnet_tla_var(vmRef *C.struct_JsonnetVm, key, value *C.char) {
|
|
vm := getVM(vmRef)
|
|
vm.TLAVar(C.GoString(key), C.GoString(value))
|
|
}
|
|
|
|
//export jsonnet_tla_code
|
|
func jsonnet_tla_code(vmRef *C.struct_JsonnetVm, key, value *C.char) {
|
|
vm := getVM(vmRef)
|
|
vm.TLACode(C.GoString(key), C.GoString(value))
|
|
}
|
|
|
|
//export jsonnet_native_callback
|
|
func jsonnet_native_callback(vmRef *C.struct_JsonnetVm, name *C.char, cb *C.JsonnetNativeCallback, ctx unsafe.Pointer, params **C.char) {
|
|
vm := getVM(vmRef)
|
|
p := unsafe.Pointer(params)
|
|
sz := unsafe.Sizeof(*params)
|
|
|
|
var paramNames ast.Identifiers
|
|
|
|
for i := 0; ; i++ {
|
|
param := (**C.char)(unsafe.Pointer(uintptr(p) + uintptr(i)*sz))
|
|
|
|
if *param == nil {
|
|
break
|
|
}
|
|
|
|
paramNames = append(paramNames, ast.Identifier(C.GoString(*param)))
|
|
}
|
|
|
|
f := &jsonnet.NativeFunction{
|
|
Name: C.GoString(name),
|
|
Params: paramNames,
|
|
Func: func(x []interface{}) (interface{}, error) {
|
|
var (
|
|
arr []*C.struct_JsonnetJsonValue
|
|
argv **C.struct_JsonnetJsonValue
|
|
success = C.int(0)
|
|
)
|
|
|
|
if len(x) > 0 {
|
|
arr = make([]*C.struct_JsonnetJsonValue, 0, len(x))
|
|
|
|
for _, json := range x {
|
|
arr = append(arr, createJSONValue(vmRef, json))
|
|
}
|
|
|
|
argv = &(arr[0])
|
|
}
|
|
|
|
result := C.jsonnet_internal_execute_native(cb, ctx, argv, &success)
|
|
v := getJSONValue(result)
|
|
|
|
for _, val := range arr {
|
|
jsonnet_json_destroy(vmRef, val)
|
|
}
|
|
|
|
jsonnet_json_destroy(vmRef, result)
|
|
|
|
if success != 1 {
|
|
return nil, fmt.Errorf("failed to execute native callback, code: %d", success)
|
|
}
|
|
|
|
return v.val, nil
|
|
},
|
|
}
|
|
|
|
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,
|
|
contentCache: make(map[string]jsonnet.Contents),
|
|
})
|
|
}
|
|
|
|
//export jsonnet_json_extract_string
|
|
func jsonnet_json_extract_string(vmRef *C.struct_JsonnetVm, json *C.struct_JsonnetJsonValue) *C.char {
|
|
v := getJSONValue(json)
|
|
str, ok := v.val.(string)
|
|
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return C.CString(str)
|
|
}
|
|
|
|
//export jsonnet_json_extract_number
|
|
func jsonnet_json_extract_number(vmRef *C.struct_JsonnetVm, json *C.struct_JsonnetJsonValue, out *C.double) C.int {
|
|
v := getJSONValue(json)
|
|
|
|
switch f := v.val.(type) {
|
|
case float64:
|
|
*out = C.double(f)
|
|
return 1
|
|
case int:
|
|
*out = C.double(f)
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
//export jsonnet_json_extract_bool
|
|
func jsonnet_json_extract_bool(vmRef *C.struct_JsonnetVm, json *C.struct_JsonnetJsonValue) C.int {
|
|
v := getJSONValue(json)
|
|
b, ok := v.val.(bool)
|
|
|
|
if !ok {
|
|
return 2
|
|
}
|
|
|
|
if b {
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
//export jsonnet_json_extract_null
|
|
func jsonnet_json_extract_null(vmRef *C.struct_JsonnetVm, json *C.struct_JsonnetJsonValue) C.int {
|
|
v := getJSONValue(json)
|
|
|
|
if v.val == nil {
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
//export jsonnet_json_make_string
|
|
func jsonnet_json_make_string(vmRef *C.struct_JsonnetVm, v *C.char) *C.struct_JsonnetJsonValue {
|
|
return createJSONValue(vmRef, C.GoString(v))
|
|
}
|
|
|
|
//export jsonnet_json_make_number
|
|
func jsonnet_json_make_number(vmRef *C.struct_JsonnetVm, v C.double) *C.struct_JsonnetJsonValue {
|
|
return createJSONValue(vmRef, float64(v))
|
|
}
|
|
|
|
//export jsonnet_json_make_bool
|
|
func jsonnet_json_make_bool(vmRef *C.struct_JsonnetVm, v C.int) *C.struct_JsonnetJsonValue {
|
|
return createJSONValue(vmRef, v != 0)
|
|
}
|
|
|
|
//export jsonnet_json_make_null
|
|
func jsonnet_json_make_null(vmRef *C.struct_JsonnetVm) *C.struct_JsonnetJsonValue {
|
|
return createJSONValue(vmRef, nil)
|
|
}
|
|
|
|
//export jsonnet_json_make_array
|
|
func jsonnet_json_make_array(vmRef *C.struct_JsonnetVm) *C.struct_JsonnetJsonValue {
|
|
return createJSONValue(vmRef, []interface{}{})
|
|
}
|
|
|
|
//export jsonnet_json_array_append
|
|
func jsonnet_json_array_append(vmRef *C.struct_JsonnetVm, arr *C.struct_JsonnetJsonValue, v *C.struct_JsonnetJsonValue) {
|
|
json := getJSONValue(arr)
|
|
slice, ok := json.val.([]interface{})
|
|
|
|
if !ok {
|
|
fmt.Fprintf(os.Stderr, "array should be provided")
|
|
os.Exit(1)
|
|
}
|
|
|
|
json.val = append(slice, getJSONValue(v).val)
|
|
json.owned = append(json.owned, v)
|
|
}
|
|
|
|
//export jsonnet_json_make_object
|
|
func jsonnet_json_make_object(vmRef *C.struct_JsonnetVm) *C.struct_JsonnetJsonValue {
|
|
return createJSONValue(vmRef, make(map[string]interface{}))
|
|
}
|
|
|
|
//export jsonnet_json_object_append
|
|
func jsonnet_json_object_append(
|
|
vmRef *C.struct_JsonnetVm,
|
|
obj *C.struct_JsonnetJsonValue,
|
|
f *C.char,
|
|
v *C.struct_JsonnetJsonValue,
|
|
) {
|
|
d := getJSONValue(obj)
|
|
table, ok := d.val.(map[string]interface{})
|
|
|
|
if !ok {
|
|
fmt.Fprintf(os.Stderr, "object should be provided")
|
|
os.Exit(1)
|
|
}
|
|
|
|
table[C.GoString(f)] = getJSONValue(v).val
|
|
d.owned = append(d.owned, v)
|
|
}
|
|
|
|
//export jsonnet_json_destroy
|
|
func jsonnet_json_destroy(vmRef *C.struct_JsonnetVm, v *C.struct_JsonnetJsonValue) {
|
|
for _, child := range getJSONValue(v).owned {
|
|
jsonnet_json_destroy(vmRef, child)
|
|
}
|
|
|
|
if err := handles.free(uintptr(v.id)); err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
C.jsonnet_internal_free_json(v)
|
|
}
|
|
|
|
func createJSONValue(vmRef *C.struct_JsonnetVm, val interface{}) *C.struct_JsonnetJsonValue {
|
|
id, err := handles.make(&jsonValue{val: val})
|
|
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
return C.jsonnet_internal_make_json_with_id(C.uintptr_t(id))
|
|
}
|
|
|
|
func getJSONValue(jsonRef *C.struct_JsonnetJsonValue) *jsonValue {
|
|
ref, err := handles.get(uintptr(jsonRef.id))
|
|
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
v, ok := ref.(*jsonValue)
|
|
|
|
if !ok {
|
|
fmt.Fprintf(os.Stderr, "provided handle has a different type")
|
|
os.Exit(1)
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
func main() {
|
|
}
|