use crate::libyml::{self, util::Owned};
use ::libyml::api::ScalarEventData;
use ::libyml::document::{
yaml_document_end_event_initialize,
yaml_document_start_event_initialize,
};
use ::libyml::YamlEventT;
use ::libyml::YamlScalarStyleT::YamlLiteralScalarStyle;
use ::libyml::{
yaml_emitter_delete, yaml_emitter_emit, yaml_emitter_flush,
yaml_emitter_initialize, yaml_emitter_set_output,
yaml_emitter_set_unicode, yaml_emitter_set_width,
yaml_mapping_end_event_initialize,
yaml_mapping_start_event_initialize, yaml_scalar_event_initialize,
yaml_sequence_end_event_initialize,
yaml_sequence_start_event_initialize,
yaml_stream_end_event_initialize,
yaml_stream_start_event_initialize, YamlAnyMappingStyle,
YamlAnySequenceStyle, YamlEmitterT, YamlScalarStyleT,
YamlSingleQuotedScalarStyle, YamlUtf8Encoding,
};
use std::fmt::Debug;
#[allow(clippy::unsafe_removed_from_name)]
use std::{
ffi::c_void,
io,
mem::{self, MaybeUninit},
ptr::{self, addr_of_mut},
slice,
};
#[derive(Debug)]
pub enum Error {
Libyml(libyml::error::Error),
Io(io::Error),
}
#[derive(Debug)]
pub struct Emitter<'a> {
pin: Owned<EmitterPinned<'a>>,
}
pub struct EmitterPinned<'a> {
sys: YamlEmitterT,
write: Box<dyn io::Write + 'a>,
write_error: Option<io::Error>,
}
impl Debug for EmitterPinned<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EmitterPinned")
.field("sys", &self.sys)
.field("write_error", &self.write_error)
.finish()
}
}
#[derive(Debug)]
pub enum Event<'a> {
StreamStart,
StreamEnd,
DocumentStart,
DocumentEnd,
Scalar(Scalar<'a>),
SequenceStart(Sequence),
SequenceEnd,
MappingStart(Mapping),
MappingEnd,
}
#[derive(Debug)]
pub struct Scalar<'a> {
pub tag: Option<String>,
pub value: &'a str,
pub style: ScalarStyle,
}
#[derive(Clone, Copy, Debug)]
pub enum ScalarStyle {
Any,
DoubleQuoted,
Folded,
Plain,
SingleQuoted,
Literal,
}
#[derive(Debug)]
pub struct Sequence {
pub tag: Option<String>,
}
#[derive(Debug)]
pub struct Mapping {
pub tag: Option<String>,
}
impl<'a> Emitter<'a> {
pub fn new(write: Box<dyn io::Write + 'a>) -> Emitter<'a> {
let owned = Owned::<EmitterPinned<'a>>::new_uninit();
let pin = unsafe {
let emitter = addr_of_mut!((*owned.ptr).sys);
if yaml_emitter_initialize(emitter).fail {
panic!(
"malloc error: {}",
libyml::Error::emit_error(emitter)
);
}
yaml_emitter_set_unicode(emitter, true);
yaml_emitter_set_width(emitter, -1);
addr_of_mut!((*owned.ptr).write).write(write);
addr_of_mut!((*owned.ptr).write_error).write(None);
yaml_emitter_set_output(
emitter,
write_handler,
owned.ptr.cast(),
);
Owned::assume_init(owned)
};
Emitter { pin }
}
pub fn emit(&mut self, event: Event<'_>) -> Result<(), Error> {
let mut sys_event = MaybeUninit::<YamlEventT>::uninit();
let sys_event = sys_event.as_mut_ptr();
unsafe {
let emitter = addr_of_mut!((*self.pin.ptr).sys);
let initialize_status = match event {
Event::StreamStart => {
yaml_stream_start_event_initialize(
sys_event,
YamlUtf8Encoding,
)
}
Event::StreamEnd => {
yaml_stream_end_event_initialize(sys_event)
}
Event::DocumentStart => {
let version_directive = ptr::null_mut();
let tag_directives_start = ptr::null_mut();
let tag_directives_end = ptr::null_mut();
let implicit = true;
yaml_document_start_event_initialize(
sys_event,
version_directive,
tag_directives_start,
tag_directives_end,
implicit,
)
}
Event::DocumentEnd => {
let implicit = true;
yaml_document_end_event_initialize(
sys_event, implicit,
)
}
Event::Scalar(mut scalar) => {
let tag_ptr = scalar.tag.as_mut().map_or_else(
ptr::null,
|tag| {
tag.push('\0');
tag.as_ptr()
},
);
let value_ptr = scalar.value.as_ptr();
let length = scalar.value.len() as i32;
let plain_implicit = tag_ptr.is_null();
let quoted_implicit = tag_ptr.is_null();
let style = match scalar.style {
ScalarStyle::Any => {
YamlScalarStyleT::YamlAnyScalarStyle
}
ScalarStyle::DoubleQuoted => {
YamlScalarStyleT::YamlDoubleQuotedScalarStyle
}
ScalarStyle::Folded => {
YamlScalarStyleT::YamlFoldedScalarStyle
}
ScalarStyle::Plain => {
YamlScalarStyleT::YamlPlainScalarStyle
}
ScalarStyle::SingleQuoted => {
YamlSingleQuotedScalarStyle
}
ScalarStyle::Literal => YamlLiteralScalarStyle,
};
let event_data = ScalarEventData {
anchor: ptr::null(),
tag: tag_ptr,
value: value_ptr,
length,
plain_implicit,
quoted_implicit,
style,
_marker: core::marker::PhantomData,
};
yaml_scalar_event_initialize(sys_event, event_data)
}
Event::SequenceStart(mut sequence) => {
let tag_ptr = sequence.tag.as_mut().map_or_else(
ptr::null,
|tag| {
tag.push('\0');
tag.as_ptr()
},
);
let implicit = tag_ptr.is_null();
let style = YamlAnySequenceStyle;
yaml_sequence_start_event_initialize(
sys_event,
ptr::null(),
tag_ptr,
implicit,
style,
)
}
Event::SequenceEnd => {
yaml_sequence_end_event_initialize(sys_event)
}
Event::MappingStart(mut mapping) => {
let tag_ptr = mapping.tag.as_mut().map_or_else(
ptr::null,
|tag| {
tag.push('\0');
tag.as_ptr()
},
);
let implicit = tag_ptr.is_null();
let style = YamlAnyMappingStyle;
yaml_mapping_start_event_initialize(
sys_event,
ptr::null(),
tag_ptr,
implicit,
style,
)
}
Event::MappingEnd => {
yaml_mapping_end_event_initialize(sys_event)
}
};
if initialize_status.fail {
return Err(Error::Libyml(libyml::Error::emit_error(
emitter,
)));
}
if yaml_emitter_emit(emitter, sys_event).fail {
return Err(self.error());
}
}
Ok(())
}
pub fn flush(&mut self) -> Result<(), Error> {
unsafe {
let emitter = addr_of_mut!((*self.pin.ptr).sys);
if yaml_emitter_flush(emitter).fail {
return Err(self.error());
}
}
Ok(())
}
#[allow(unused_mut)]
pub fn into_inner(mut self) -> Box<dyn io::Write + 'a> {
let sink = Box::new(io::sink());
unsafe { mem::replace(&mut (*self.pin.ptr).write, sink) }
}
pub fn error(&mut self) -> Error {
let emitter = unsafe { &mut *self.pin.ptr };
if let Some(write_error) = emitter.write_error.take() {
Error::Io(write_error)
} else {
Error::Libyml(unsafe {
libyml::Error::emit_error(&emitter.sys)
})
}
}
}
unsafe fn write_handler(
data: *mut c_void,
buffer: *mut u8,
size: u64,
) -> i32 {
let data = data.cast::<EmitterPinned<'_>>();
match io::Write::write_all(unsafe { &mut *(*data).write }, unsafe {
slice::from_raw_parts(buffer, size as usize)
}) {
Ok(()) => 1,
Err(err) => {
unsafe {
(*data).write_error = Some(err);
}
0
}
}
}
impl Drop for EmitterPinned<'_> {
fn drop(&mut self) {
unsafe { yaml_emitter_delete(&mut self.sys) }
}
}