use std::ptr::NonNull;
use crate::editor::{Segment, borrow_str, to_ffi_keys, to_ffi_path};
use crate::error::Error;
use crate::value::{Value, value_text, value_text_with};
use crate::{Format, SerializeOptions, ffi};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EmbedType {
FrontmatterYaml,
FrontmatterJson,
EndmatterYaml,
FrontmatterFig,
}
impl EmbedType {
fn ffi(self) -> ffi::FigEmbedType {
match self {
EmbedType::FrontmatterYaml => ffi::FigEmbedType::FrontmatterYaml,
EmbedType::FrontmatterJson => ffi::FigEmbedType::FrontmatterJson,
EmbedType::EndmatterYaml => ffi::FigEmbedType::EndmatterYaml,
EmbedType::FrontmatterFig => ffi::FigEmbedType::FrontmatterFig,
}
}
fn from_ffi(value: i32) -> Option<Self> {
match value {
v if v == ffi::FigEmbedType::FrontmatterYaml as i32 => Some(EmbedType::FrontmatterYaml),
v if v == ffi::FigEmbedType::FrontmatterJson as i32 => Some(EmbedType::FrontmatterJson),
v if v == ffi::FigEmbedType::EndmatterYaml as i32 => Some(EmbedType::EndmatterYaml),
v if v == ffi::FigEmbedType::FrontmatterFig as i32 => Some(EmbedType::FrontmatterFig),
_ => None,
}
}
pub fn inner_format(self) -> Format {
match self {
EmbedType::FrontmatterYaml | EmbedType::EndmatterYaml => Format::Yaml,
EmbedType::FrontmatterJson => Format::Json,
EmbedType::FrontmatterFig => Format::Fig,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl From<ffi::FigSpan> for Span {
fn from(s: ffi::FigSpan) -> Self {
Span { start: s.start, end: s.end }
}
}
#[derive(Clone, Copy, Debug)]
pub struct Region {
pub open_fence: Span,
pub content: Span,
pub close_fence: Span,
pub body: Span,
}
#[derive(Clone, Copy, Debug)]
pub struct Extracted<'a> {
source: &'a str,
region: Region,
}
impl<'a> Extracted<'a> {
pub fn region(&self) -> Region {
self.region
}
pub fn content(&self) -> &'a str {
&self.source[self.region.content.start..self.region.content.end]
}
pub fn body(&self) -> &'a str {
&self.source[self.region.body.start..self.region.body.end]
}
}
pub fn split(content: &str, kind: EmbedType) -> Option<(&str, &str)> {
let e = Embed::extract(content, kind).ok()?;
Some((e.content(), e.body()))
}
pub fn detect(source: &str) -> Option<EmbedType> {
let mut out: std::os::raw::c_int = 0;
let status = unsafe { ffi::fig_embed_detect(source.as_ptr(), source.len(), &mut out) };
if status.0 != ffi::FigStatus::OK {
return None;
}
EmbedType::from_ffi(out)
}
#[derive(Debug)]
pub struct Embed {
raw: NonNull<ffi::FigEmbed>,
inner: Format,
}
impl Embed {
pub fn open(host: &[u8], kind: EmbedType) -> Result<Self, Error> {
let mut raw = std::ptr::null_mut();
let status =
unsafe { ffi::fig_embed_open(host.as_ptr(), host.len(), kind.ffi() as i32, &mut raw) };
Error::from_status(status)?;
let raw = NonNull::new(raw).ok_or(Error::Internal)?;
Ok(Self {
raw,
inner: kind.inner_format(),
})
}
pub fn open_or_init(host: &[u8], kind: EmbedType) -> Result<Self, Error> {
let mut raw = std::ptr::null_mut();
let status = unsafe {
ffi::fig_embed_open_or_init(host.as_ptr(), host.len(), kind.ffi() as i32, &mut raw)
};
Error::from_status(status)?;
let raw = NonNull::new(raw).ok_or(Error::Internal)?;
Ok(Self {
raw,
inner: kind.inner_format(),
})
}
pub fn extract(content: &str, kind: EmbedType) -> Result<Extracted<'_>, Error> {
let mut region = ffi::FigRegion {
size: core::mem::size_of::<ffi::FigRegion>() as u32,
..Default::default()
};
let status = unsafe {
ffi::fig_embed_extract(content.as_ptr(), content.len(), kind.ffi() as i32, &mut region)
};
Error::from_status(status)?;
Ok(Extracted {
source: content,
region: Region {
open_fence: region.open_fence.into(),
content: region.content.into(),
close_fence: region.close_fence.into(),
body: region.body.into(),
},
})
}
fn ptr(&self) -> *mut ffi::FigEmbed {
self.raw.as_ptr()
}
pub fn replace_value(&mut self, path: &[Segment], value: impl Into<Value>) -> Result<(), Error> {
let repl = value_text(&value.into(), self.inner)?;
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_replace_val(self.ptr(), p.as_ptr(), p.len(), repl.as_ptr(), repl.len())
};
Error::from_status(status)
}
pub fn replace_key(&mut self, path: &[Segment], key: &str) -> Result<(), Error> {
let repl = value_text(&Value::Str(key.to_string()), self.inner)?;
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_replace_key(self.ptr(), p.as_ptr(), p.len(), repl.as_ptr(), repl.len())
};
Error::from_status(status)
}
pub fn insert_value(
&mut self,
path: &[Segment],
key: &str,
value: impl Into<Value>,
) -> Result<(), Error> {
let key_text = value_text(&Value::Str(key.to_string()), self.inner)?;
let val = value_text(&value.into(), self.inner)?;
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_insert_key(
self.ptr(),
p.as_ptr(),
p.len(),
key_text.as_ptr(),
key_text.len(),
val.as_ptr(),
val.len(),
)
};
Error::from_status(status)
}
pub fn set_value(&mut self, path: &[Segment], value: impl Into<Value>) -> Result<(), Error> {
let val = value_text(&value.into(), self.inner)?;
let p = to_ffi_path(path);
let status =
unsafe { ffi::fig_embed_set(self.ptr(), p.as_ptr(), p.len(), val.as_ptr(), val.len()) };
Error::from_status(status)
}
pub fn replace_value_with(
&mut self,
path: &[Segment],
value: impl Into<Value>,
options: SerializeOptions,
) -> Result<(), Error> {
let repl = value_text_with(&value.into(), self.inner, options)?;
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_replace_val(self.ptr(), p.as_ptr(), p.len(), repl.as_ptr(), repl.len())
};
Error::from_status(status)
}
pub fn insert_value_with(
&mut self,
path: &[Segment],
key: &str,
value: impl Into<Value>,
options: SerializeOptions,
) -> Result<(), Error> {
let key_text = value_text(&Value::Str(key.to_string()), self.inner)?;
let val = value_text_with(&value.into(), self.inner, options)?;
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_insert_key(
self.ptr(),
p.as_ptr(),
p.len(),
key_text.as_ptr(),
key_text.len(),
val.as_ptr(),
val.len(),
)
};
Error::from_status(status)
}
pub fn set_value_with(
&mut self,
path: &[Segment],
value: impl Into<Value>,
options: SerializeOptions,
) -> Result<(), Error> {
let val = value_text_with(&value.into(), self.inner, options)?;
let p = to_ffi_path(path);
let status =
unsafe { ffi::fig_embed_set(self.ptr(), p.as_ptr(), p.len(), val.as_ptr(), val.len()) };
Error::from_status(status)
}
pub fn append_value(&mut self, path: &[Segment], value: impl Into<Value>) -> Result<(), Error> {
let val = value_text(&value.into(), self.inner)?;
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_append_seq(self.ptr(), p.as_ptr(), p.len(), val.as_ptr(), val.len())
};
Error::from_status(status)
}
pub fn prepend_value(&mut self, path: &[Segment], value: impl Into<Value>) -> Result<(), Error> {
let val = value_text(&value.into(), self.inner)?;
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_prepend_seq(self.ptr(), p.as_ptr(), p.len(), val.as_ptr(), val.len())
};
Error::from_status(status)
}
#[cfg(feature = "serde")]
pub fn replace<T: serde::Serialize + ?Sized>(
&mut self,
path: &[Segment],
value: &T,
) -> Result<(), Error> {
self.replace_value(path, crate::ser::to_value(value)?)
}
#[cfg(feature = "serde")]
pub fn insert<T: serde::Serialize + ?Sized>(
&mut self,
path: &[Segment],
key: &str,
value: &T,
) -> Result<(), Error> {
self.insert_value(path, key, crate::ser::to_value(value)?)
}
#[cfg(feature = "serde")]
pub fn set<T: serde::Serialize + ?Sized>(
&mut self,
path: &[Segment],
value: &T,
) -> Result<(), Error> {
self.set_value(path, crate::ser::to_value(value)?)
}
#[cfg(feature = "serde")]
pub fn append<T: serde::Serialize + ?Sized>(
&mut self,
path: &[Segment],
value: &T,
) -> Result<(), Error> {
self.append_value(path, crate::ser::to_value(value)?)
}
#[cfg(feature = "serde")]
pub fn prepend<T: serde::Serialize + ?Sized>(
&mut self,
path: &[Segment],
value: &T,
) -> Result<(), Error> {
self.prepend_value(path, crate::ser::to_value(value)?)
}
pub fn add_leading_comment(&mut self, path: &[Segment], text: &str) -> Result<(), Error> {
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_add_leading_comment(self.ptr(), p.as_ptr(), p.len(), text.as_ptr(), text.len())
};
Error::from_status(status)
}
pub fn set_trailing_comment(&mut self, path: &[Segment], text: &str) -> Result<(), Error> {
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_set_trailing_comment(self.ptr(), p.as_ptr(), p.len(), text.as_ptr(), text.len())
};
Error::from_status(status)
}
pub fn delete_leading_comments(&mut self, path: &[Segment]) -> Result<(), Error> {
let p = to_ffi_path(path);
let status = unsafe { ffi::fig_embed_delete_leading_comments(self.ptr(), p.as_ptr(), p.len()) };
Error::from_status(status)
}
pub fn delete_trailing_comment(&mut self, path: &[Segment]) -> Result<(), Error> {
let p = to_ffi_path(path);
let status = unsafe { ffi::fig_embed_delete_trailing_comment(self.ptr(), p.as_ptr(), p.len()) };
Error::from_status(status)
}
pub fn leading_comment(&self, path: &[Segment]) -> Result<Option<String>, Error> {
self.read_comment(path, false)
}
pub fn trailing_comment(&self, path: &[Segment]) -> Result<Option<String>, Error> {
self.read_comment(path, true)
}
fn read_comment(&self, path: &[Segment], trailing: bool) -> Result<Option<String>, Error> {
let p = to_ffi_path(path);
let mut ptr: *const u8 = std::ptr::null();
let mut len: usize = 0;
let status = unsafe {
if trailing {
ffi::fig_embed_get_trailing_comment(self.ptr(), p.as_ptr(), p.len(), &mut ptr, &mut len)
} else {
ffi::fig_embed_get_leading_comment(self.ptr(), p.as_ptr(), p.len(), &mut ptr, &mut len)
}
};
if status.0 == ffi::FigStatus::NOT_FOUND {
return Ok(None);
}
Error::from_status(status)?;
Ok(Some(borrow_str(ptr, len)?.to_string()))
}
pub fn delete(&mut self, path: &[Segment]) -> Result<(), Error> {
let p = to_ffi_path(path);
let status = unsafe { ffi::fig_embed_delete_key(self.ptr(), p.as_ptr(), p.len()) };
Error::from_status(status)
}
pub fn remove_item(&mut self, path: &[Segment], index: usize) -> Result<(), Error> {
let p = to_ffi_path(path);
let status =
unsafe { ffi::fig_embed_remove_seq_item(self.ptr(), p.as_ptr(), p.len(), index) };
Error::from_status(status)
}
pub fn move_key(&mut self, src_path: &[Segment], dest_path: &[Segment]) -> Result<(), Error> {
let s = to_ffi_path(src_path);
let d = to_ffi_path(dest_path);
let status = unsafe {
ffi::fig_embed_move_key(self.ptr(), s.as_ptr(), s.len(), d.as_ptr(), d.len())
};
Error::from_status(status)
}
pub fn reorder_keys<S: AsRef<str>>(
&mut self,
path: &[Segment],
keys: &[S],
) -> Result<(), Error> {
let p = to_ffi_path(path);
let k = to_ffi_keys(keys);
let status = unsafe {
ffi::fig_embed_reorder_keys(self.ptr(), p.as_ptr(), p.len(), k.as_ptr(), k.len())
};
Error::from_status(status)
}
pub fn move_item(&mut self, path: &[Segment], from: usize, to: usize) -> Result<(), Error> {
let p = to_ffi_path(path);
let status = unsafe { ffi::fig_embed_move_item(self.ptr(), p.as_ptr(), p.len(), from, to) };
Error::from_status(status)
}
pub fn reorder_items(&mut self, path: &[Segment], indices: &[usize]) -> Result<(), Error> {
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_reorder_items(
self.ptr(),
p.as_ptr(),
p.len(),
indices.as_ptr(),
indices.len(),
)
};
Error::from_status(status)
}
pub fn set_sequence(&mut self, path: &[Segment], items: &[Value]) -> Result<(), Error> {
let texts: Vec<String> = items
.iter()
.map(|v| value_text(v, self.inner))
.collect::<Result<_, _>>()?;
let strs = to_ffi_keys(&texts);
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_set_sequence(self.ptr(), p.as_ptr(), p.len(), strs.as_ptr(), strs.len())
};
Error::from_status(status)
}
pub fn replace_body(&mut self, body: &str) -> Result<(), Error> {
let status =
unsafe { ffi::fig_embed_replace_body(self.ptr(), body.as_ptr(), body.len()) };
Error::from_status(status)
}
pub fn render(&mut self) -> Result<&str, Error> {
let mut ptr: *const u8 = std::ptr::null();
let mut len: usize = 0;
let status = unsafe { ffi::fig_embed_render(self.raw.as_ptr(), &mut ptr, &mut len) };
Error::from_status(status)?;
borrow_str(ptr, len)
}
}
impl Drop for Embed {
fn drop(&mut self) {
unsafe { ffi::fig_embed_destroy(self.raw.as_ptr()) };
}
}