use std::fmt::Write as _;
#[non_exhaustive]
#[derive(Debug)]
pub struct FormatConfig<'a> {
pub indent_level: usize,
pub indent: &'a str,
pub no_comments: bool,
pub entry_autoformate_keep: bool,
}
impl Default for FormatConfig<'_> {
fn default() -> Self {
Self::builder().build()
}
}
impl FormatConfig<'_> {
pub const fn builder() -> FormatConfigBuilder<'static> {
FormatConfigBuilder::new()
}
}
#[derive(Debug, Default)]
pub struct FormatConfigBuilder<'a>(FormatConfig<'a>);
impl<'a> FormatConfigBuilder<'a> {
pub const fn new() -> Self {
Self(FormatConfig {
indent_level: 0,
indent: " ",
no_comments: false,
entry_autoformate_keep: false,
})
}
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
}
pub const fn indent_level(mut self, indent_level: usize) -> Self {
self.0.indent_level = indent_level;
self
}
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
}
}
pub const fn indent(self, indent: &str) -> FormatConfigBuilder<'_> {
FormatConfigBuilder(FormatConfig { indent, ..self.0 })
}
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
}
pub const fn no_comments(mut self, no_comments: bool) -> Self {
self.0.no_comments = no_comments;
self
}
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(())
}
}