use crate::config;
use crate::diag::{diag_error, Diag};
use crate::editor::Editor;
use crate::error::{Error, Result};
use crate::ffi_util::{malloc_copy, take_c_string};
use crate::node_ref::NodeRef;
use crate::value_ref::ValueRef;
use fyaml_sys::*;
use libc::c_void;
use std::fmt;
use std::marker::PhantomData;
use std::ptr::{self, NonNull};
use std::str::FromStr;
pub(crate) use crate::parser::ParserInner;
#[allow(dead_code)]
pub enum InputOwnership {
LibfyamlOwned,
OwnedString(String),
OwnedBytes(Vec<u8>),
Parser(std::rc::Rc<ParserInner>),
None,
}
pub struct Document {
pub(crate) doc_ptr: NonNull<fy_document>,
#[allow(dead_code)]
input: InputOwnership,
_marker: PhantomData<*mut ()>,
}
impl Document {
#[inline]
pub(crate) fn from_raw_ptr(doc_ptr: NonNull<fy_document>, input: InputOwnership) -> Self {
Document {
doc_ptr,
input,
_marker: PhantomData,
}
}
pub fn new() -> Result<Self> {
let doc_ptr = unsafe { fy_document_create(ptr::null_mut()) };
let nn = NonNull::new(doc_ptr).ok_or(Error::Ffi("fy_document_create returned null"))?;
Ok(Document {
doc_ptr: nn,
input: InputOwnership::None,
_marker: PhantomData,
})
}
pub fn from_stdin() -> Result<Self> {
use crate::FyParser;
let parser = FyParser::from_stdin()?;
parser
.doc_iter()
.next()
.ok_or(Error::Parse("no document in stdin stream"))?
}
pub fn parse_str(s: &str) -> Result<Self> {
if s.is_empty() {
return Err(Error::Parse("empty input"));
}
let buf = unsafe { malloc_copy(s.as_bytes())? };
let diag = Diag::new();
let diag_ptr = diag.as_ref().map(|d| d.as_ptr()).unwrap_or(ptr::null_mut());
let cfg = config::document_parse_cfg_with_diag(diag_ptr);
let doc_ptr = unsafe { fy_document_build_from_malloc_string(&cfg, buf, s.len()) };
if doc_ptr.is_null() {
unsafe { libc::free(buf as *mut c_void) };
return Err(diag_error(
diag,
"fy_document_build_from_malloc_string failed",
));
}
Ok(Document {
doc_ptr: NonNull::new(doc_ptr).unwrap(),
input: InputOwnership::LibfyamlOwned,
_marker: PhantomData,
})
}
pub fn from_string(s: String) -> Result<Self> {
if s.is_empty() {
return Err(Error::Parse("empty input"));
}
let diag = Diag::new();
let diag_ptr = diag.as_ref().map(|d| d.as_ptr()).unwrap_or(ptr::null_mut());
let cfg = config::document_parse_cfg_with_diag(diag_ptr);
let doc_ptr =
unsafe { fy_document_build_from_string(&cfg, s.as_ptr() as *const i8, s.len()) };
if doc_ptr.is_null() {
return Err(diag_error(diag, "fy_document_build_from_string failed"));
}
Ok(Document {
doc_ptr: NonNull::new(doc_ptr).unwrap(),
input: InputOwnership::OwnedString(s),
_marker: PhantomData,
})
}
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
if bytes.is_empty() {
return Err(Error::Parse("empty input"));
}
let diag = Diag::new();
let diag_ptr = diag.as_ref().map(|d| d.as_ptr()).unwrap_or(ptr::null_mut());
let cfg = config::document_parse_cfg_with_diag(diag_ptr);
let doc_ptr = unsafe {
fy_document_build_from_string(&cfg, bytes.as_ptr() as *const i8, bytes.len())
};
if doc_ptr.is_null() {
return Err(diag_error(diag, "fy_document_build_from_string failed"));
}
Ok(Document {
doc_ptr: NonNull::new(doc_ptr).unwrap(),
input: InputOwnership::OwnedBytes(bytes),
_marker: PhantomData,
})
}
#[inline]
pub fn root(&self) -> Option<NodeRef<'_>> {
let node_ptr = unsafe { fy_document_root(self.doc_ptr.as_ptr()) };
NonNull::new(node_ptr).map(|nn| NodeRef::new(nn, self))
}
#[inline]
pub fn at_path(&self, path: &str) -> Option<NodeRef<'_>> {
self.root()?.at_path(path)
}
#[inline]
pub fn root_value(&self) -> Option<ValueRef<'_>> {
self.root().map(ValueRef::new)
}
#[inline]
pub fn edit(&mut self) -> Editor<'_> {
Editor::new(self)
}
pub fn emit(&self) -> Result<String> {
let ptr =
unsafe { fy_emit_document_to_string(self.doc_ptr.as_ptr(), config::emit_flags()) };
if ptr.is_null() {
return Err(Error::Ffi("fy_emit_document_to_string returned null"));
}
Ok(unsafe { take_c_string(ptr) })
}
#[inline]
pub(crate) fn as_ptr(&self) -> *mut fy_document {
self.doc_ptr.as_ptr()
}
}
impl Drop for Document {
fn drop(&mut self) {
log::trace!("Dropping Document {:p}", self.doc_ptr.as_ptr());
match &self.input {
InputOwnership::Parser(parser_inner) => {
unsafe { fy_parse_document_destroy(parser_inner.as_ptr(), self.doc_ptr.as_ptr()) };
}
_ => {
unsafe { fy_document_destroy(self.doc_ptr.as_ptr()) };
}
}
}
}
impl Default for Document {
fn default() -> Self {
Self::new().expect("Failed to create default document")
}
}
impl FromStr for Document {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::parse_str(s)
}
}
impl fmt::Display for Document {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.emit() {
Ok(s) => write!(f, "{}", s),
Err(_) => write!(f, "<Document emit error>"),
}
}
}
impl fmt::Debug for Document {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Document")
.field("ptr", &self.doc_ptr)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple() {
let doc = Document::parse_str("foo: bar").unwrap();
assert!(doc.root().is_some());
}
#[test]
fn test_parse_empty_fails() {
let result = Document::parse_str("");
assert!(result.is_err());
}
#[test]
fn test_new_empty_document() {
let doc = Document::new().unwrap();
assert!(doc.root().is_none());
}
#[test]
fn test_at_path() {
let doc = Document::parse_str("foo:\n bar: baz").unwrap();
let node = doc.at_path("/foo/bar").unwrap();
assert_eq!(node.scalar_str().unwrap(), "baz");
}
#[test]
fn test_emit() {
let doc = Document::parse_str("foo: bar").unwrap();
let yaml = doc.emit().unwrap();
assert!(yaml.contains("foo"));
assert!(yaml.contains("bar"));
}
}