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)]
#[non_exhaustive]
pub enum EmbedType {
FrontmatterYaml,
FrontmatterJson,
EndmatterYaml,
FrontmatterFig,
PlusToml,
FencedYaml,
FencedJson,
FencedToml,
MdFrontmatterJson,
MdFrontmatterToml,
MdFrontmatterFig,
HtmlScriptFig,
HtmlScriptYaml,
HtmlScriptJson,
HtmlScriptToml,
HtmlCodeFig,
HtmlCodeYaml,
HtmlCodeJson,
HtmlCodeToml,
}
impl EmbedType {
fn parts(self) -> (ffi::FigEmbedContainer, ffi::FigFormat) {
use ffi::FigEmbedContainer as C;
use ffi::FigFormat as F;
match self {
EmbedType::FrontmatterYaml => (C::MdFrontmatter, F::Yaml),
EmbedType::FrontmatterJson => (C::SemicolonsJson, F::Json),
EmbedType::EndmatterYaml => (C::EndmatterYaml, F::Yaml),
EmbedType::FrontmatterFig => (C::Fenced, F::Fig),
EmbedType::PlusToml => (C::PlusToml, F::Toml),
EmbedType::FencedYaml => (C::Fenced, F::Yaml),
EmbedType::FencedJson => (C::Fenced, F::Json),
EmbedType::FencedToml => (C::Fenced, F::Toml),
EmbedType::MdFrontmatterJson => (C::MdFrontmatter, F::Json),
EmbedType::MdFrontmatterToml => (C::MdFrontmatter, F::Toml),
EmbedType::MdFrontmatterFig => (C::MdFrontmatter, F::Fig),
EmbedType::HtmlScriptFig => (C::HtmlScript, F::Fig),
EmbedType::HtmlScriptYaml => (C::HtmlScript, F::Yaml),
EmbedType::HtmlScriptJson => (C::HtmlScript, F::Json),
EmbedType::HtmlScriptToml => (C::HtmlScript, F::Toml),
EmbedType::HtmlCodeFig => (C::HtmlCode, F::Fig),
EmbedType::HtmlCodeYaml => (C::HtmlCode, F::Yaml),
EmbedType::HtmlCodeJson => (C::HtmlCode, F::Json),
EmbedType::HtmlCodeToml => (C::HtmlCode, F::Toml),
}
}
fn from_parts(container: i32, format: i32) -> Option<Self> {
use ffi::FigEmbedContainer as C;
use ffi::FigFormat as F;
let c = match container {
v if v == C::MdFrontmatter as i32 => C::MdFrontmatter,
v if v == C::Fenced as i32 => C::Fenced,
v if v == C::HtmlScript as i32 => C::HtmlScript,
v if v == C::HtmlCode as i32 => C::HtmlCode,
v if v == C::SemicolonsJson as i32 => return Some(EmbedType::FrontmatterJson),
v if v == C::PlusToml as i32 => return Some(EmbedType::PlusToml),
v if v == C::EndmatterYaml as i32 => return Some(EmbedType::EndmatterYaml),
_ => return None,
};
let f = match format {
v if v == F::Yaml as i32 => F::Yaml,
v if v == F::Json as i32 => F::Json,
v if v == F::Toml as i32 => F::Toml,
v if v == F::Fig as i32 => F::Fig,
_ => return None,
};
Some(match (c, f) {
(C::MdFrontmatter, F::Yaml) => EmbedType::FrontmatterYaml,
(C::MdFrontmatter, F::Json) => EmbedType::MdFrontmatterJson,
(C::MdFrontmatter, F::Toml) => EmbedType::MdFrontmatterToml,
(C::MdFrontmatter, F::Fig) => EmbedType::MdFrontmatterFig,
(C::Fenced, F::Yaml) => EmbedType::FencedYaml,
(C::Fenced, F::Json) => EmbedType::FencedJson,
(C::Fenced, F::Toml) => EmbedType::FencedToml,
(C::Fenced, F::Fig) => EmbedType::FrontmatterFig,
(C::HtmlScript, F::Yaml) => EmbedType::HtmlScriptYaml,
(C::HtmlScript, F::Json) => EmbedType::HtmlScriptJson,
(C::HtmlScript, F::Toml) => EmbedType::HtmlScriptToml,
(C::HtmlScript, F::Fig) => EmbedType::HtmlScriptFig,
(C::HtmlCode, F::Yaml) => EmbedType::HtmlCodeYaml,
(C::HtmlCode, F::Json) => EmbedType::HtmlCodeJson,
(C::HtmlCode, F::Toml) => EmbedType::HtmlCodeToml,
(C::HtmlCode, F::Fig) => EmbedType::HtmlCodeFig,
_ => return None,
})
}
pub fn inner_format(self) -> Format {
match self {
EmbedType::FrontmatterYaml
| EmbedType::EndmatterYaml
| EmbedType::FencedYaml
| EmbedType::HtmlScriptYaml
| EmbedType::HtmlCodeYaml => Format::Yaml,
EmbedType::FrontmatterJson
| EmbedType::FencedJson
| EmbedType::MdFrontmatterJson
| EmbedType::HtmlScriptJson
| EmbedType::HtmlCodeJson => Format::Json,
EmbedType::FrontmatterFig
| EmbedType::MdFrontmatterFig
| EmbedType::HtmlScriptFig
| EmbedType::HtmlCodeFig => Format::Fig,
EmbedType::PlusToml
| EmbedType::FencedToml
| EmbedType::MdFrontmatterToml
| EmbedType::HtmlScriptToml
| EmbedType::HtmlCodeToml => Format::Toml,
}
}
}
#[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)]
#[non_exhaustive]
pub struct Region {
pub open_fence: Span,
pub content: Span,
pub close_fence: Span,
pub body: Span,
pub body_before: Span,
pub body_after: 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 host_before(&self) -> &'a str {
&self.source[self.region.body_before.start..self.region.body_before.end]
}
pub fn host_after(&self) -> &'a str {
&self.source[self.region.body_after.start..self.region.body_after.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 container: std::os::raw::c_int = 0;
let mut format: std::os::raw::c_int = 0;
let status = unsafe {
ffi::fig_embed_detect(source.as_ptr(), source.len(), &mut container, &mut format)
};
if status.0 != ffi::FigStatus::OK {
return None;
}
EmbedType::from_parts(container, format)
}
#[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 (container, format) = kind.parts();
let status = unsafe {
ffi::fig_embed_open(host.as_ptr(), host.len(), container as i32, format 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 (container, format) = kind.parts();
let status = unsafe {
ffi::fig_embed_open_or_init(
host.as_ptr(),
host.len(),
container as i32,
format 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 retype(
host: &str,
from: EmbedType,
to: EmbedType,
content: &str,
) -> Result<String, Error> {
let mut ptr: *mut u8 = std::ptr::null_mut();
let mut len: usize = 0;
let (from_container, from_format) = from.parts();
let (to_container, to_format) = to.parts();
let status = unsafe {
ffi::fig_embed_retype(
host.as_ptr(),
host.len(),
from_container as i32,
from_format as i32,
to_container as i32,
to_format as i32,
content.as_ptr(),
content.len(),
&mut ptr,
&mut len,
)
};
Error::from_status(status)?;
if ptr.is_null() {
return Err(Error::Internal);
}
let owned = unsafe { std::slice::from_raw_parts(ptr, len) }.to_vec();
unsafe { ffi::fig_free(ptr, len) };
String::from_utf8(owned).map_err(|_| Error::Utf8)
}
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 (container, format) = kind.parts();
let status = unsafe {
ffi::fig_embed_extract(
content.as_ptr(),
content.len(),
container as i32,
format 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(),
body_before: region.body_before.into(),
body_after: region.body_after.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 val = value_text(&value.into(), self.inner)?;
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_insert_named_key(
self.ptr(),
p.as_ptr(),
p.len(),
key.as_ptr(),
key.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 val = value_text_with(&value.into(), self.inner, options)?;
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_insert_named_key(
self.ptr(),
p.as_ptr(),
p.len(),
key.as_ptr(),
key.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 add_dangling_comment(&mut self, path: &[Segment], text: &str) -> Result<(), Error> {
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_add_dangling_comment(
self.ptr(),
p.as_ptr(),
p.len(),
text.as_ptr(),
text.len(),
)
};
Error::from_status(status)
}
pub fn delete_dangling_comments(&mut self, path: &[Segment]) -> Result<(), Error> {
let p = to_ffi_path(path);
let status =
unsafe { ffi::fig_embed_delete_dangling_comments(self.ptr(), p.as_ptr(), p.len()) };
Error::from_status(status)
}
pub fn dangling_comment(&self, path: &[Segment]) -> 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 {
ffi::fig_embed_get_dangling_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 comment_out(&mut self, path: &[Segment]) -> Result<(), Error> {
let p = to_ffi_path(path);
let status = unsafe { ffi::fig_embed_comment_out(self.ptr(), p.as_ptr(), p.len()) };
Error::from_status(status)
}
pub fn uncomment_leading(
&mut self,
path: &[Segment],
first_line: usize,
line_count: usize,
) -> Result<(), Error> {
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_uncomment_leading(
self.ptr(),
p.as_ptr(),
p.len(),
first_line,
line_count,
)
};
Error::from_status(status)
}
pub fn uncomment_dangling(
&mut self,
path: &[Segment],
first_line: usize,
line_count: usize,
) -> Result<(), Error> {
let p = to_ffi_path(path);
let status = unsafe {
ffi::fig_embed_uncomment_dangling(
self.ptr(),
p.as_ptr(),
p.len(),
first_line,
line_count,
)
};
Error::from_status(status)
}
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()) };
}
}