#![deny(missing_docs)]
#![allow(trivial_casts)]
use libc::{c_char, c_int, c_uint, c_void};
use std::ffi::{CStr, CString, OsStr};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::{error, fmt, iter, ptr};
mod string;
mod value;
pub use crate::string::JsonnetString;
use crate::string::JsonnetStringIter;
pub use crate::value::{JsonVal, JsonValue};
use jsonnet_sys::JsonnetJsonValue;
#[derive(Debug, PartialEq, Eq)]
pub struct Error<'a>(JsonnetString<'a>);
impl<'a> From<JsonnetString<'a>> for Error<'a> {
fn from(s: JsonnetString<'a>) -> Self {
Error(s)
}
}
impl<'a> From<Error<'a>> for JsonnetString<'a> {
fn from(e: Error<'a>) -> Self {
e.0
}
}
impl<'a> Deref for Error<'a> {
type Target = JsonnetString<'a>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> fmt::Display for Error<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl<'a> error::Error for Error<'a> {
fn description(&self) -> &str {
"jsonnet error"
}
}
pub struct EvalMap<'a>(JsonnetString<'a>);
impl<'a> EvalMap<'a> {
pub fn iter<'b>(&'b self) -> EvalMapIter<'b, 'a> {
EvalMapIter(unsafe { JsonnetStringIter::new(&self.0) })
}
}
impl<'a, 'b> IntoIterator for &'b EvalMap<'a> {
type Item = (&'b str, &'b str);
type IntoIter = EvalMapIter<'b, 'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug)]
pub struct EvalMapIter<'a, 'b: 'a>(JsonnetStringIter<'a, 'b>);
impl<'a, 'b> Iterator for EvalMapIter<'a, 'b> {
type Item = (&'a str, &'a str);
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|first| {
let second = self.0.next().unwrap();
(first, second)
})
}
}
pub struct EvalList<'a>(JsonnetString<'a>);
impl<'a> EvalList<'a> {
pub fn iter<'b>(&'b self) -> EvalListIter<'b, 'a> {
EvalListIter(unsafe { JsonnetStringIter::new(&self.0) })
}
}
pub struct EvalListIter<'a, 'b: 'a>(JsonnetStringIter<'a, 'b>);
impl<'a, 'b> Iterator for EvalListIter<'a, 'b> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl<'a, 'b> IntoIterator for &'b EvalList<'a> {
type Item = &'b str;
type IntoIter = EvalListIter<'b, 'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum FmtString {
Double,
Single,
Leave,
}
impl FmtString {
fn to_char(self) -> c_int {
let c = match self {
FmtString::Double => 'd',
FmtString::Single => 's',
FmtString::Leave => 'l',
};
c as c_int
}
}
impl Default for FmtString {
fn default() -> FmtString {
FmtString::Leave
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum FmtComment {
Hash,
Slash,
Leave,
}
impl FmtComment {
fn to_char(self) -> c_int {
let c = match self {
FmtComment::Hash => 'h',
FmtComment::Slash => 's',
FmtComment::Leave => 'l',
};
c as c_int
}
}
impl Default for FmtComment {
fn default() -> FmtComment {
FmtComment::Leave
}
}
pub fn jsonnet_version() -> &'static str {
let s = unsafe { CStr::from_ptr(jsonnet_sys::jsonnet_version()) };
s.to_str().unwrap()
}
#[test]
fn test_version() {
let s = jsonnet_version();
println!("Got version: {}", s);
assert_eq!(s.split('.').count(), 3);
}
struct ImportContext<'a, F> {
vm: &'a JsonnetVm,
cb: F,
}
#[cfg(unix)]
fn bytes2osstr(b: &[u8]) -> &OsStr {
use std::os::unix::ffi::OsStrExt;
OsStr::from_bytes(b)
}
#[cfg(windows)]
fn bytes2osstr(b: &[u8]) -> &OsStr {
let s = std::str::from_utf8(b).unwrap();
OsStr::new(s)
}
fn cstr2osstr(cstr: &CStr) -> &OsStr {
bytes2osstr(cstr.to_bytes())
}
#[cfg(unix)]
fn osstr2bytes(os: &OsStr) -> &[u8] {
use std::os::unix::ffi::OsStrExt;
os.as_bytes()
}
#[cfg(windows)]
fn osstr2bytes(os: &OsStr) -> &[u8] {
os.to_str().unwrap().as_bytes()
}
fn osstr2cstring<T: AsRef<OsStr>>(os: T) -> CString {
let b = osstr2bytes(os.as_ref());
CString::new(b).unwrap()
}
extern "C" fn import_callback<F>(
ctx: *mut c_void,
base: &c_char,
rel: &c_char,
found_here: &mut *mut c_char,
success: &mut c_int,
) -> *mut c_char
where
F: Fn(&JsonnetVm, &Path, &Path) -> Result<(PathBuf, String), String>,
{
let ctx = unsafe { &*(ctx as *mut ImportContext<F>) };
let vm = ctx.vm;
let callback = &ctx.cb;
let base_path = Path::new(cstr2osstr(unsafe { CStr::from_ptr(base) }));
let rel_path = Path::new(cstr2osstr(unsafe { CStr::from_ptr(rel) }));
match callback(vm, base_path, rel_path) {
Ok((found_here_path, contents)) => {
*success = 1;
let v = {
let b = osstr2bytes(found_here_path.as_os_str());
JsonnetString::from_bytes(vm, b)
};
*found_here = v.into_raw();
JsonnetString::new(vm, &contents).into_raw()
}
Err(err) => {
*success = 0;
JsonnetString::new(vm, &err).into_raw()
}
}
}
struct NativeContext<'a, F> {
vm: &'a JsonnetVm,
argc: usize,
cb: F,
}
extern "C" fn native_callback<'a, F>(
ctx: *mut libc::c_void,
argv: *const *const JsonnetJsonValue,
success: &mut c_int,
) -> *mut JsonnetJsonValue
where
F: Fn(&'a JsonnetVm, &[JsonVal<'a>]) -> Result<JsonValue<'a>, String>,
{
let ctx = unsafe { &*(ctx as *mut NativeContext<F>) };
let vm = ctx.vm;
let callback = &ctx.cb;
let args: Vec<_> = (0..ctx.argc)
.map(|i| unsafe { JsonVal::from_ptr(vm, *argv.offset(i as isize)) })
.collect();
match callback(vm, &args) {
Ok(v) => {
*success = 1;
v.into_raw()
}
Err(err) => {
*success = 0;
let ret = JsonValue::from_str(vm, &err);
ret.into_raw()
}
}
}
pub struct JsonnetVm(*mut jsonnet_sys::JsonnetVm);
impl JsonnetVm {
pub fn new() -> Self {
let vm = unsafe { jsonnet_sys::jsonnet_make() };
JsonnetVm(vm)
}
fn as_ptr(&self) -> *mut jsonnet_sys::JsonnetVm {
self.0
}
pub fn max_stack(&mut self, v: u32) {
unsafe { jsonnet_sys::jsonnet_max_stack(self.0, v as c_uint) }
}
pub fn gc_min_objects(&mut self, v: u32) {
unsafe { jsonnet_sys::jsonnet_gc_min_objects(self.0, v as c_uint) }
}
pub fn gc_growth_trigger(&mut self, v: f64) {
unsafe { jsonnet_sys::jsonnet_gc_growth_trigger(self.0, v) }
}
pub fn string_output(&mut self, v: bool) {
unsafe { jsonnet_sys::jsonnet_string_output(self.0, v as c_int) }
}
pub fn import_callback<F>(&mut self, cb: F)
where
F: Fn(&JsonnetVm, &Path, &Path) -> Result<(PathBuf, String), String>,
{
let ctx = ImportContext { vm: self, cb: cb };
unsafe {
jsonnet_sys::jsonnet_import_callback(
self.as_ptr(),
import_callback::<F> as *const _,
Box::into_raw(Box::new(ctx)) as *mut _,
);
}
}
pub fn native_callback<F>(&mut self, name: &str, cb: F, params: &[&str])
where
for<'a> F: Fn(&'a JsonnetVm, &[JsonVal<'a>]) -> Result<JsonValue<'a>, String>,
{
let ctx = NativeContext {
vm: self,
argc: params.len(),
cb: cb,
};
let cname = CString::new(name).unwrap();
let cparams: Vec<_> = params
.into_iter()
.map(|&p| CString::new(p).unwrap())
.collect();
let cptrs: Vec<_> = cparams
.iter()
.map(|p| p.as_ptr())
.chain(iter::once(ptr::null()))
.collect();
unsafe {
jsonnet_sys::jsonnet_native_callback(
self.as_ptr(),
cname.as_ptr(),
native_callback::<F> as *const _,
Box::into_raw(Box::new(ctx)) as *mut _,
cptrs.as_slice().as_ptr(),
);
}
}
pub fn ext_var(&mut self, key: &str, val: &str) {
let ckey = CString::new(key).unwrap();
let cval = CString::new(val).unwrap();
unsafe {
jsonnet_sys::jsonnet_ext_var(self.0, ckey.as_ptr(), cval.as_ptr());
}
}
pub fn ext_code(&mut self, key: &str, code: &str) {
let ckey = CString::new(key).unwrap();
let ccode = CString::new(code).unwrap();
unsafe {
jsonnet_sys::jsonnet_ext_code(self.0, ckey.as_ptr(), ccode.as_ptr());
}
}
pub fn tla_var(&mut self, key: &str, val: &str) {
let ckey = CString::new(key).unwrap();
let cval = CString::new(val).unwrap();
unsafe {
jsonnet_sys::jsonnet_tla_var(self.0, ckey.as_ptr(), cval.as_ptr());
}
}
pub fn tla_code(&mut self, key: &str, code: &str) {
let ckey = CString::new(key).unwrap();
let ccode = CString::new(code).unwrap();
unsafe {
jsonnet_sys::jsonnet_tla_code(self.0, ckey.as_ptr(), ccode.as_ptr());
}
}
pub fn fmt_indent(&mut self, n: u32) {
unsafe {
jsonnet_sys::jsonnet_fmt_indent(self.0, n as c_int);
}
}
pub fn fmt_max_blank_lines(&mut self, n: u32) {
unsafe {
jsonnet_sys::jsonnet_fmt_max_blank_lines(self.0, n as c_int);
}
}
pub fn fmt_string(&mut self, fmt: FmtString) {
unsafe {
jsonnet_sys::jsonnet_fmt_string(self.0, fmt.to_char());
}
}
pub fn fmt_comment(&mut self, fmt: FmtComment) {
unsafe {
jsonnet_sys::jsonnet_fmt_comment(self.0, fmt.to_char());
}
}
pub fn fmt_pad_arrays(&mut self, pad: bool) {
unsafe {
jsonnet_sys::jsonnet_fmt_pad_arrays(self.0, pad as c_int);
}
}
pub fn fmt_pad_objects(&mut self, pad: bool) {
unsafe {
jsonnet_sys::jsonnet_fmt_pad_objects(self.0, pad as c_int);
}
}
pub fn fmt_pretty_field_names(&mut self, sugar: bool) {
unsafe {
jsonnet_sys::jsonnet_fmt_pretty_field_names(self.0, sugar as c_int);
}
}
pub fn fmt_sort_import(&mut self, sort: bool) {
unsafe {
jsonnet_sys::jsonnet_fmt_sort_imports(self.0, sort as c_int);
}
}
pub fn fmt_debug_desugaring(&mut self, reformat: bool) {
unsafe {
jsonnet_sys::jsonnet_fmt_debug_desugaring(self.0, reformat as c_int);
}
}
pub fn fmt_file<'a, P>(&'a mut self, filename: P) -> Result<JsonnetString<'a>, Error<'a>>
where
P: AsRef<OsStr>,
{
let fname = osstr2cstring(filename);
let mut error = 1;
let output = unsafe {
let v = jsonnet_sys::jsonnet_fmt_file(self.0, fname.as_ptr(), &mut error);
JsonnetString::from_ptr(self, v)
};
match error {
0 => Ok(output),
_ => Err(Error(output)),
}
}
pub fn fmt_snippet<'a, P>(
&'a mut self,
filename: P,
snippet: &str,
) -> Result<JsonnetString<'a>, Error<'a>>
where
P: AsRef<OsStr>,
{
let fname = osstr2cstring(filename);
let snippet = CString::new(snippet).unwrap();
let mut error = 1;
let output = unsafe {
let v = jsonnet_sys::jsonnet_fmt_snippet(
self.0,
fname.as_ptr(),
snippet.as_ptr(),
&mut error,
);
JsonnetString::from_ptr(self, v)
};
match error {
0 => Ok(output),
_ => Err(Error(output)),
}
}
pub fn max_trace(&mut self, limit: Option<u32>) {
let v = limit.unwrap_or(0);
unsafe {
jsonnet_sys::jsonnet_max_trace(self.0, v);
}
}
pub fn jpath_add<P>(&mut self, path: P)
where
P: AsRef<OsStr>,
{
let v = osstr2cstring(path);
unsafe {
jsonnet_sys::jsonnet_jpath_add(self.0, v.as_ptr());
}
}
pub fn evaluate_file<'a, P>(&'a mut self, filename: P) -> Result<JsonnetString<'a>, Error<'a>>
where
P: AsRef<OsStr>,
{
let fname = osstr2cstring(filename);
let mut error = 1;
let output = unsafe {
let v = jsonnet_sys::jsonnet_evaluate_file(self.0, fname.as_ptr(), &mut error);
JsonnetString::from_ptr(self, v)
};
match error {
0 => Ok(output),
_ => Err(Error(output)),
}
}
pub fn evaluate_snippet<'a, P>(
&'a mut self,
filename: P,
snippet: &str,
) -> Result<JsonnetString<'a>, Error<'a>>
where
P: AsRef<OsStr>,
{
let fname = osstr2cstring(filename);
let snip = CString::new(snippet).unwrap();
let mut error = 1;
let output = unsafe {
let v = jsonnet_sys::jsonnet_evaluate_snippet(
self.0,
fname.as_ptr(),
snip.as_ptr(),
&mut error,
);
JsonnetString::from_ptr(self, v)
};
match error {
0 => Ok(output),
_ => Err(Error(output)),
}
}
pub fn evaluate_file_multi<'a, P>(&'a mut self, filename: P) -> Result<EvalMap<'a>, Error<'a>>
where
P: AsRef<OsStr>,
{
let fname = osstr2cstring(filename);
let mut error = 1;
let output = unsafe {
let v = jsonnet_sys::jsonnet_evaluate_file_multi(self.0, fname.as_ptr(), &mut error);
JsonnetString::from_ptr(self, v)
};
match error {
0 => Ok(EvalMap(output)),
_ => Err(Error(output)),
}
}
pub fn evaluate_snippet_multi<'a, P>(
&'a mut self,
filename: P,
snippet: &str,
) -> Result<EvalMap<'a>, Error<'a>>
where
P: AsRef<OsStr>,
{
let fname = osstr2cstring(filename);
let snippet = CString::new(snippet).unwrap();
let mut error = 1;
let output = unsafe {
let v = jsonnet_sys::jsonnet_evaluate_snippet_multi(
self.0,
fname.as_ptr(),
snippet.as_ptr(),
&mut error,
);
JsonnetString::from_ptr(self, v)
};
match error {
0 => Ok(EvalMap(output)),
_ => Err(Error(output)),
}
}
pub fn evaluate_file_stream<'a, P>(&'a mut self, filename: P) -> Result<EvalList<'a>, Error<'a>>
where
P: AsRef<OsStr>,
{
let fname = osstr2cstring(filename);
let mut error = 1;
let output = unsafe {
let v = jsonnet_sys::jsonnet_evaluate_file_stream(self.0, fname.as_ptr(), &mut error);
JsonnetString::from_ptr(self, v)
};
match error {
0 => Ok(EvalList(output)),
_ => Err(Error(output)),
}
}
pub fn evaluate_snippet_stream<'a, P>(
&'a mut self,
filename: P,
snippet: &str,
) -> Result<EvalList<'a>, Error<'a>>
where
P: AsRef<OsStr>,
{
let fname = osstr2cstring(filename);
let snippet = CString::new(snippet).unwrap();
let mut error = 1;
let output = unsafe {
let v = jsonnet_sys::jsonnet_evaluate_snippet_stream(
self.0,
fname.as_ptr(),
snippet.as_ptr(),
&mut error,
);
JsonnetString::from_ptr(self, v)
};
match error {
0 => Ok(EvalList(output)),
_ => Err(Error(output)),
}
}
}
impl Drop for JsonnetVm {
fn drop(&mut self) {
assert!(!self.0.is_null());
unsafe { jsonnet_sys::jsonnet_destroy(self.0) }
}
}
#[test]
fn basic_eval() {
let mut vm = JsonnetVm::new();
let result = vm.evaluate_snippet("example", "'Hello ' + 'World'");
println!("result is {:?}", result);
assert!(result.is_ok());
assert_eq!(result.unwrap().to_string(), "\"Hello World\"\n");
}
#[test]
fn basic_eval_err() {
let mut vm = JsonnetVm::new();
let result = vm.evaluate_snippet("example", "bogus");
println!("result is {:?}", result);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Unknown variable: bogus"));
}