pub mod config;
pub mod error;
mod escape_placeholders;
pub mod file_resolver;
pub mod formatter;
pub mod incremental_cache;
pub mod parser;
mod preservation;
pub mod settings;
pub mod skills;
pub mod transform;
pub mod typography;
pub mod wrapping;
use std::path::{Path, PathBuf};
pub use config::{DEFAULT_WRAP_WIDTH, FormatOptions, ListSpacing};
pub use error::{Error, Result};
pub use formatter::filling::fill_markdown;
pub use wrapping::line_wrappers::{line_wrap_by_sentence, line_wrap_to_width};
pub use wrapping::sentence::split_sentences_regex;
pub use wrapping::text_filling::{Wrap, fill_text};
pub use wrapping::text_wrapping::{
html_md_word_split, simple_word_split, wrap_paragraph, wrap_paragraph_lines,
};
impl FormatOptions {
pub fn reformat_text(&self, text: &str) -> String {
if self.plaintext {
let wrap = if self.width > 0 { Wrap::Wrap } else { Wrap::None };
fill_text(text, wrap, self.width, "", "", 0, None)
} else {
fill_markdown(
text,
false,
self.width,
self.semantic,
self.cleanups,
self.smartquotes,
self.ellipses,
None,
self.list_spacing,
)
}
}
pub fn reformat_bytes(&self, bytes: &[u8]) -> Result<String> {
let text = std::str::from_utf8(bytes).map_err(|source| {
Error::Io(std::io::Error::new(std::io::ErrorKind::InvalidData, source))
})?;
Ok(self.reformat_text(text))
}
pub fn reformat_file(
&self,
path: &Path,
output: Option<&Path>,
inplace: bool,
nobackup: bool,
) -> Result<()> {
let content = std::fs::read(path)?;
let formatted = self.reformat_bytes(&content)?;
if inplace && formatted.as_bytes() == content {
return Ok(());
}
if inplace {
if !nobackup {
let mut backup_name = path.as_os_str().to_os_string();
backup_name.push(".orig");
let backup_path = PathBuf::from(backup_name);
std::fs::copy(path, &backup_path)?;
}
atomic_write(path, &formatted)?;
} else if let Some(out) = output {
if let Some(parent) = out.parent() {
std::fs::create_dir_all(parent)?;
}
atomic_write(out, &formatted)?;
} else {
print!("{formatted}");
}
Ok(())
}
}
#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
pub fn reformat_text(
text: &str,
width: usize,
plaintext: bool,
semantic: bool,
cleanups: bool,
smartquotes: bool,
ellipses: bool,
list_spacing: ListSpacing,
) -> String {
let opts =
FormatOptions { width, plaintext, semantic, cleanups, smartquotes, ellipses, list_spacing };
opts.reformat_text(text)
}
#[allow(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
pub fn reformat_file(
path: &Path,
output: Option<&Path>,
width: usize,
inplace: bool,
nobackup: bool,
plaintext: bool,
semantic: bool,
cleanups: bool,
smartquotes: bool,
ellipses: bool,
list_spacing: ListSpacing,
) -> Result<()> {
let opts =
FormatOptions { width, plaintext, semantic, cleanups, smartquotes, ellipses, list_spacing };
opts.reformat_file(path, output, inplace, nobackup)
}
fn atomic_write(path: &Path, content: &str) -> Result<()> {
use std::io::Write;
#[cfg(unix)]
let original_permissions = path.metadata().ok().map(|m| m.permissions());
let dir = path.parent().unwrap_or(Path::new("."));
let mut tmp = tempfile::NamedTempFile::new_in(dir)?;
tmp.write_all(content.as_bytes())?;
tmp.persist(path).map_err(|e| Error::Io(e.error))?;
#[cfg(unix)]
if let Some(perms) = original_permissions {
std::fs::set_permissions(path, perms)?;
}
Ok(())
}