use std::borrow::Cow;
use deser_core::Text;
use deser_core::de::{
self, Deserialize, DeserializeDriver, DuplicateKeys, LexicalRules, deserialize_value,
};
use deser_core::{Atom, ContainerShape, Error, ErrorKind, Event, Source, TrackLocations};
use crate::parser::{self, Document, NodeKind, Range};
use crate::{Continuation, InlineComments, Quotes, Syntax};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeserializerConfig {
pub(crate) syntax: Syntax,
pub(crate) inline_comments: InlineComments,
pub(crate) colon_delimiter: bool,
pub(crate) continuation: Continuation,
pub(crate) quotes: Quotes,
pub(crate) allow_no_value: bool,
pub(crate) lowercase_names: bool,
context: deser_core::Context,
}
impl Default for DeserializerConfig {
fn default() -> DeserializerConfig {
DeserializerConfig::new()
}
}
impl DeserializerConfig {
pub const fn new() -> DeserializerConfig {
DeserializerConfig {
syntax: Syntax::Ini,
inline_comments: InlineComments::AfterWhitespace,
colon_delimiter: true,
continuation: Continuation::Indented,
quotes: Quotes::Value,
allow_no_value: true,
lowercase_names: false,
context: deser_core::Context::new(),
}
}
pub const fn python() -> DeserializerConfig {
DeserializerConfig::builder()
.inline_comments(InlineComments::None)
.quotes(Quotes::None)
.build()
}
pub const fn git() -> DeserializerConfig {
DeserializerConfig::builder().syntax(Syntax::Git).build()
}
pub const fn builder() -> DeserializerConfigBuilder {
DeserializerConfigBuilder::new()
}
pub const fn into_builder(self) -> DeserializerConfigBuilder {
DeserializerConfigBuilder { value: self }
}
pub fn set_context(&mut self, context: deser_core::Context) {
self.context = context;
}
pub(crate) fn without_context(&self) -> DeserializerConfig {
let mut config = self.clone();
config.context = deser_core::Context::default();
config
}
pub fn context(&self) -> &deser_core::Context {
&self.context
}
pub const fn set_syntax(&mut self, syntax: Syntax) {
self.syntax = syntax;
}
pub const fn syntax(&self) -> Syntax {
self.syntax
}
pub const fn set_inline_comments(&mut self, comments: InlineComments) {
self.inline_comments = comments;
}
pub const fn inline_comments(&self) -> InlineComments {
self.inline_comments
}
pub const fn set_colon_delimiter(&mut self, yes: bool) {
self.colon_delimiter = yes;
}
pub const fn colon_delimiter(&self) -> bool {
self.colon_delimiter
}
pub const fn set_continuation(&mut self, continuation: Continuation) {
self.continuation = continuation;
}
pub const fn continuation(&self) -> Continuation {
self.continuation
}
pub const fn set_quotes(&mut self, quotes: Quotes) {
self.quotes = quotes;
}
pub const fn quotes(&self) -> Quotes {
self.quotes
}
pub const fn set_allow_no_value(&mut self, yes: bool) {
self.allow_no_value = yes;
}
pub const fn allow_no_value(&self) -> bool {
self.allow_no_value
}
pub const fn set_lowercase_names(&mut self, yes: bool) {
self.lowercase_names = yes;
}
pub const fn lowercase_names(&self) -> bool {
self.lowercase_names
}
pub fn from_str<'de, T: Deserialize<'de>>(&self, s: &'de str) -> Result<T, Error> {
deserialize_value(|driver| self.drive_str(s, driver))
}
fn drive_str<'de>(
&self,
s: &'de str,
driver: &mut DeserializeDriver<'_, 'de>,
) -> Result<(), Error> {
de::Deserializer::drive(
&mut Deserializer::from_str_with_config(s, self.clone()),
driver,
)
}
pub fn from_slice<'de, T: Deserialize<'de>>(&self, bytes: &'de [u8]) -> Result<T, Error> {
deserialize_value(|driver| self.drive_slice(bytes, driver))
}
fn drive_slice<'de>(
&self,
bytes: &'de [u8],
driver: &mut DeserializeDriver<'_, 'de>,
) -> Result<(), Error> {
de::Deserializer::drive(
&mut Deserializer::from_slice_with_config(bytes, self.clone()),
driver,
)
}
}
#[derive(Debug, Clone)]
#[must_use]
pub struct DeserializerConfigBuilder {
value: DeserializerConfig,
}
impl DeserializerConfigBuilder {
pub const fn new() -> DeserializerConfigBuilder {
DeserializerConfigBuilder {
value: DeserializerConfig::new(),
}
}
pub const fn syntax(mut self, syntax: Syntax) -> DeserializerConfigBuilder {
self.value.set_syntax(syntax);
self
}
pub const fn inline_comments(mut self, comments: InlineComments) -> DeserializerConfigBuilder {
self.value.set_inline_comments(comments);
self
}
pub const fn colon_delimiter(mut self, yes: bool) -> DeserializerConfigBuilder {
self.value.set_colon_delimiter(yes);
self
}
pub const fn continuation(mut self, continuation: Continuation) -> DeserializerConfigBuilder {
self.value.set_continuation(continuation);
self
}
pub const fn quotes(mut self, quotes: Quotes) -> DeserializerConfigBuilder {
self.value.set_quotes(quotes);
self
}
pub const fn allow_no_value(mut self, yes: bool) -> DeserializerConfigBuilder {
self.value.set_allow_no_value(yes);
self
}
pub const fn lowercase_names(mut self, yes: bool) -> DeserializerConfigBuilder {
self.value.set_lowercase_names(yes);
self
}
pub fn context(mut self, context: deser_core::Context) -> DeserializerConfigBuilder {
self.value.set_context(context);
self
}
pub const fn build(self) -> DeserializerConfig {
let value = unsafe { core::ptr::read(&self.value) };
core::mem::forget(self);
value
}
}
impl Default for DeserializerConfigBuilder {
fn default() -> DeserializerConfigBuilder {
DeserializerConfigBuilder::new()
}
}
pub struct Deserializer<'a> {
input: &'a str,
error: Option<Error>,
config: DeserializerConfig,
}
impl<'a> Deserializer<'a> {
#[allow(clippy::should_implement_trait)]
pub fn from_str(input: &'a str) -> Deserializer<'a> {
Deserializer::from_str_with_config(input, DeserializerConfig::new())
}
pub fn from_str_with_config(input: &'a str, config: DeserializerConfig) -> Deserializer<'a> {
Deserializer {
input,
error: None,
config,
}
}
pub fn from_slice(input: &'a [u8]) -> Deserializer<'a> {
Deserializer::from_slice_with_config(input, DeserializerConfig::new())
}
pub fn from_slice_with_config(input: &'a [u8], config: DeserializerConfig) -> Deserializer<'a> {
match std::str::from_utf8(input) {
Ok(input) => Deserializer::from_str_with_config(input, config),
Err(err) => Deserializer {
input: "",
error: Some(Error::with_offset(
ErrorKind::Syntax,
"input is not valid UTF-8",
err.valid_up_to(),
)),
config,
},
}
}
pub fn config(&self) -> &DeserializerConfig {
&self.config
}
pub fn deserialize<T: Deserialize<'a>>(&mut self) -> Result<T, Error> {
de::Deserializer::deserialize(self)
}
pub fn deserialize_with<T, F>(&mut self, setup: F) -> Result<T, Error>
where
T: Deserialize<'a>,
F: FnOnce(&mut DeserializeDriver<'_, 'a>),
{
de::Deserializer::deserialize_with(self, setup)
}
pub fn drive(&mut self, driver: &mut DeserializeDriver<'_, 'a>) -> Result<(), Error> {
if !self.config.context.is_empty() {
driver.set_default_context(self.config.context.clone());
}
if let Some(err) = self.error.take() {
return Err(err);
}
let doc = parser::parse(self.input, &self.config).map_err(|mut err| {
err.resolve_position(self.input.as_bytes());
err
})?;
let state = driver.state_mut();
if TrackLocations::of(state) {
Source(self.input.into()).set(state);
}
DuplicateKeys::Last.set_default(state);
LexicalRules::LENIENT.set_default(state);
emit(&doc, self.input.len(), driver).map_err(|mut err| {
err.resolve_position(self.input.as_bytes());
err
})
}
}
impl<'a> de::Deserializer<'a> for Deserializer<'a> {
fn drive(&mut self, driver: &mut DeserializeDriver<'_, 'a>) -> Result<(), Error> {
Deserializer::drive(self, driver)
}
}
fn table_shape(doc: &Document<'_>, children: &[usize]) -> ContainerShape {
let len = children
.iter()
.map(|&child| doc.nodes[child].values.len().max(1))
.sum();
let mut shape = ContainerShape::with_len(len);
shape.set_multimap(true);
shape
}
fn emit<'a>(
doc: &Document<'a>,
input_len: usize,
driver: &mut DeserializeDriver<'_, 'a>,
) -> Result<(), Error> {
struct Frame {
node: usize,
pos: usize,
}
let root = &doc.nodes[0];
emit_at(
driver,
Event::MapStart(table_shape(doc, &root.children)),
root.range,
)?;
let mut stack = vec![Frame { node: 0, pos: 0 }];
while let Some(frame) = stack.last_mut() {
let table = &doc.nodes[frame.node];
let Some(&child) = table.children.get(frame.pos) else {
let range = if frame.node == 0 {
(input_len, input_len)
} else {
table.range
};
stack.pop();
emit_at(driver, Event::MapEnd, range)?;
continue;
};
frame.pos += 1;
let node = &doc.nodes[child];
match node.kind {
NodeKind::Key => {
for (value, range) in &node.values {
emit_text(driver, &node.name, node.range)?;
match *value {
Some(ref value) => emit_text(driver, value, *range)?,
None => emit_at(driver, Atom::Null, *range)?,
}
}
}
NodeKind::Table => {
emit_text(driver, &node.name, node.range)?;
emit_at(
driver,
Event::MapStart(table_shape(doc, &node.children)),
node.range,
)?;
stack.push(Frame {
node: child,
pos: 0,
});
}
}
}
Ok(())
}
#[inline]
fn emit_at<'e, E: Into<Event<'e>>>(
driver: &mut DeserializeDriver<'_, '_>,
event: E,
range: Range,
) -> Result<(), Error> {
driver.state_mut().set_input_range(range.0, range.1);
driver.emit(event)
}
#[allow(clippy::ptr_arg)]
#[inline]
fn emit_text<'a>(
driver: &mut DeserializeDriver<'_, 'a>,
text: &Cow<'a, str>,
range: Range,
) -> Result<(), Error> {
driver.state_mut().set_input_range(range.0, range.1);
match *text {
Cow::Borrowed(text) => driver.emit_borrowed(Atom::Lexical(Text::borrowed(text))),
Cow::Owned(ref text) => driver.emit(Atom::Lexical(Text::borrowed(text.as_str()))),
}
}