Skip to main content

just_kdl/
writer.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Write an event stream to an output.
3//!
4//! You probably want to start at [`Writer`].
5
6// TODO: is there much use for a "minified" writer?
7
8use core::fmt;
9
10use crate::IdentDisplay;
11use crate::dom::Event;
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash)]
14/// Writer of document events.
15pub struct Writer<W> {
16	inner: W,
17	state: State,
18	indent: usize,
19	indent_text: &'static str,
20}
21
22// since the writer doesn't support comments, there's no need to track a
23// comment-depth stack
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25enum State {
26	First,
27	BlockStart,
28	Block,
29	Node,
30}
31
32impl<W: fmt::Write> Writer<W> {
33	/// Create a new writer with an output.
34	pub const fn new(writer: W) -> Self {
35		Self {
36			inner: writer,
37			state: State::First,
38			indent: 0,
39			indent_text: "    ",
40		}
41	}
42	/// Change the text inserted for each indentation level,
43	/// default is four spaces
44	pub fn set_indent(&mut self, indent: &'static str) { self.indent_text = indent; }
45	fn line(&mut self) -> fmt::Result {
46		writeln!(self.inner)?;
47		for _ in 0..self.indent {
48			write!(self.inner, "{}", self.indent_text)?;
49		}
50		Ok(())
51	}
52	/// Write an event to the writer
53	/// # Errors
54	/// If the inner writer errors
55	pub fn push(&mut self, event: &Event) -> fmt::Result {
56		match event {
57			Event::Node { r#type, name } => {
58				if self.state != State::First {
59					self.line()?;
60				}
61				self.state = State::Node;
62				if let Some(r#type) = r#type {
63					write!(self.inner, "({})", IdentDisplay(r#type))?;
64				}
65				write!(self.inner, "{}", IdentDisplay(name))
66			}
67			Event::Entry(entry) => write!(self.inner, " {entry}"),
68			Event::Children => {
69				self.indent += 1;
70				self.state = State::BlockStart;
71				write!(self.inner, " {{")
72			}
73			Event::End => {
74				match self.state {
75					State::BlockStart => {
76						self.indent -= 1;
77						write!(self.inner, "}}")?;
78					}
79					State::Block => {
80						self.indent -= 1;
81						self.line()?;
82						write!(self.inner, "}}")?;
83					}
84					_ => {}
85				}
86				self.state = State::Block;
87				Ok(())
88			}
89		}
90	}
91}
92
93/// Bridge from [`fmt::Write`] to [`std::io::Write`], for writing to IO streams.
94#[cfg(feature = "std")]
95#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
96#[derive(Clone, Debug, PartialEq, Eq, Hash)]
97pub struct WriteOutput<T>(pub T);
98
99#[cfg(feature = "std")]
100#[expect(clippy::absolute_paths, reason = "feature-gated")]
101impl<T: std::io::Write> fmt::Write for WriteOutput<T> {
102	fn write_str(&mut self, s: &str) -> fmt::Result {
103		self.0.write_all(s.as_bytes()).map_err(|_| fmt::Error)
104	}
105}