mirror of https://github.com/kdl-org/kdl-rs.git
176 lines
5.0 KiB
Rust
176 lines
5.0 KiB
Rust
use std::fmt::Write as _;
|
|
|
|
/// Formatting configuration for use with [`KdlDocument::autoformat_config`](`crate::KdlDocument::autoformat_config`)
|
|
/// and [`KdlNode::autoformat_config`](`crate::KdlNode::autoformat_config`).
|
|
#[non_exhaustive]
|
|
#[derive(Debug)]
|
|
pub struct FormatConfig<'a> {
|
|
/// How deeply to indent the overall node or document,
|
|
/// in repetitions of [`indent`](`FormatConfig::indent`).
|
|
/// Defaults to `0`.
|
|
pub indent_level: usize,
|
|
|
|
/// The indentation to use at each level. Defaults to four spaces.
|
|
pub indent: &'a str,
|
|
|
|
/// Whether to remove comments. Defaults to `false`.
|
|
pub no_comments: bool,
|
|
|
|
/// Whether to keep individual entry formatting.
|
|
pub entry_autoformate_keep: bool,
|
|
}
|
|
|
|
/// See field documentation for defaults.
|
|
impl Default for FormatConfig<'_> {
|
|
fn default() -> Self {
|
|
Self::builder().build()
|
|
}
|
|
}
|
|
|
|
impl FormatConfig<'_> {
|
|
/// Creates a new [`FormatConfigBuilder`] with default configuration.
|
|
pub const fn builder() -> FormatConfigBuilder<'static> {
|
|
FormatConfigBuilder::new()
|
|
}
|
|
}
|
|
|
|
/// A [`FormatConfig`] builder.
|
|
///
|
|
/// Note that setters can be repeated.
|
|
#[derive(Debug, Default)]
|
|
pub struct FormatConfigBuilder<'a>(FormatConfig<'a>);
|
|
|
|
impl<'a> FormatConfigBuilder<'a> {
|
|
/// Creates a new [`FormatConfig`] builder with default configuration.
|
|
pub const fn new() -> Self {
|
|
Self(FormatConfig {
|
|
indent_level: 0,
|
|
indent: " ",
|
|
no_comments: false,
|
|
entry_autoformate_keep: false,
|
|
})
|
|
}
|
|
|
|
/// How deeply to indent the overall node or document,
|
|
/// in repetitions of [`indent`](`FormatConfig::indent`).
|
|
/// Defaults to `0` iff not specified.
|
|
pub const fn maybe_indent_level(mut self, indent_level: Option<usize>) -> Self {
|
|
if let Some(indent_level) = indent_level {
|
|
self.0.indent_level = indent_level;
|
|
}
|
|
self
|
|
}
|
|
|
|
/// How deeply to indent the overall node or document,
|
|
/// in repetitions of [`indent`](`FormatConfig::indent`).
|
|
/// Defaults to `0` iff not specified.
|
|
pub const fn indent_level(mut self, indent_level: usize) -> Self {
|
|
self.0.indent_level = indent_level;
|
|
self
|
|
}
|
|
|
|
/// The indentation to use at each level.
|
|
/// Defaults to four spaces iff not specified.
|
|
pub const fn maybe_indent<'b, 'c>(self, indent: Option<&'b str>) -> FormatConfigBuilder<'c>
|
|
where
|
|
'a: 'b,
|
|
'b: 'c,
|
|
{
|
|
if let Some(indent) = indent {
|
|
self.indent(indent)
|
|
} else {
|
|
self
|
|
}
|
|
}
|
|
|
|
/// The indentation to use at each level.
|
|
/// Defaults to four spaces if not specified.
|
|
pub const fn indent(self, indent: &str) -> FormatConfigBuilder<'_> {
|
|
FormatConfigBuilder(FormatConfig { indent, ..self.0 })
|
|
}
|
|
|
|
/// Whether to remove comments.
|
|
/// Defaults to `false` iff not specified.
|
|
pub const fn maybe_no_comments(mut self, no_comments: Option<bool>) -> Self {
|
|
if let Some(no_comments) = no_comments {
|
|
self.0.no_comments = no_comments;
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Whether to remove comments.
|
|
/// Defaults to `false` iff not specified.
|
|
pub const fn no_comments(mut self, no_comments: bool) -> Self {
|
|
self.0.no_comments = no_comments;
|
|
self
|
|
}
|
|
|
|
/// Builds the [`FormatConfig`].
|
|
pub const fn build(self) -> FormatConfig<'a> {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
pub(crate) fn autoformat_leading(leading: &mut String, config: &FormatConfig<'_>) {
|
|
let mut result = String::new();
|
|
if !config.no_comments {
|
|
let input = leading.trim();
|
|
if !input.is_empty() {
|
|
for line in input.lines() {
|
|
let trimmed = line.trim();
|
|
if !trimmed.is_empty() {
|
|
for _ in 0..config.indent_level {
|
|
result.push_str(config.indent);
|
|
}
|
|
writeln!(result, "{trimmed}").unwrap();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for _ in 0..config.indent_level {
|
|
result.push_str(config.indent);
|
|
}
|
|
*leading = result;
|
|
}
|
|
|
|
pub(crate) fn autoformat_trailing(decor: &mut String, no_comments: bool) {
|
|
if decor.is_empty() {
|
|
return;
|
|
}
|
|
*decor = decor.trim().to_string();
|
|
let mut result = String::new();
|
|
if !decor.is_empty() && !no_comments {
|
|
if decor.trim_start() == &decor[..] {
|
|
write!(result, " ").unwrap();
|
|
}
|
|
for comment in decor.lines() {
|
|
writeln!(result, "{comment}").unwrap();
|
|
}
|
|
}
|
|
*decor = result;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn builder() -> miette::Result<()> {
|
|
let built = FormatConfig::builder()
|
|
.indent_level(12)
|
|
.indent(" \t")
|
|
.no_comments(true)
|
|
.build();
|
|
assert!(matches!(
|
|
built,
|
|
FormatConfig {
|
|
indent_level: 12,
|
|
indent: " \t",
|
|
no_comments: true,
|
|
entry_autoformate_keep: false,
|
|
}
|
|
));
|
|
Ok(())
|
|
}
|
|
}
|