kotoba_formatter/
formatter.rs1use super::{FormatterConfig, FormatResult, FormatRules};
4use std::path::PathBuf;
5use tokio::fs;
6
7#[derive(Debug)]
9pub struct CodeFormatter {
10 config: FormatterConfig,
11 rules: FormatRules,
12}
13
14impl CodeFormatter {
15 pub fn new(config: FormatterConfig) -> Self {
17 let rules = FormatRules::new(&config);
18 Self { config, rules }
19 }
20
21 pub fn config(&self) -> &FormatterConfig {
23 &self.config
24 }
25
26 pub fn set_config(&mut self, config: FormatterConfig) {
28 self.config = config.clone();
29 self.rules = FormatRules::new(&config);
30 }
31
32 pub async fn format_file(&self, file_path: &PathBuf) -> Result<FormatResult, Box<dyn std::error::Error>> {
34 let content = fs::read_to_string(file_path).await?;
35 let mut result = FormatResult::new(file_path.clone(), content);
36
37 match self.format_content(&result.original_content).await {
38 Ok(formatted) => {
39 result.set_formatted_content(formatted);
40 Ok(result)
41 }
42 Err(e) => {
43 result.set_error(e.to_string());
44 Ok(result)
45 }
46 }
47 }
48
49 pub async fn format_content(&self, content: &str) -> Result<String, Box<dyn std::error::Error>> {
51 let formatted = self.rules.apply(content)?;
53
54 let cleaned = self.final_cleanup(&formatted);
56
57 Ok(cleaned)
58 }
59
60 fn final_cleanup(&self, content: &str) -> String {
62 let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
63
64 while let Some(line) = lines.last() {
66 if line.trim().is_empty() {
67 lines.pop();
68 } else {
69 break;
70 }
71 }
72
73 for line in &mut lines {
75 *line = line.trim_end().to_string();
76 }
77
78 lines.join(&self.get_line_ending())
79 }
80
81 fn get_line_ending(&self) -> String {
83 match self.config.line_ending {
84 super::LineEnding::Lf => "\n".to_string(),
85 super::LineEnding::Crlf => "\r\n".to_string(),
86 super::LineEnding::Auto => "\n".to_string(),
87 }
88 }
89}
90
91pub async fn format_file_with_config(
93 file_path: &PathBuf,
94 config: &FormatterConfig,
95) -> Result<FormatResult, Box<dyn std::error::Error>> {
96 let formatter = CodeFormatter::new(config.clone());
97 formatter.format_file(file_path).await
98}
99
100pub async fn format_content_with_config(
101 content: &str,
102 config: &FormatterConfig,
103) -> Result<String, Box<dyn std::error::Error>> {
104 let formatter = CodeFormatter::new(config.clone());
105 formatter.format_content(content).await
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_formatter_creation() {
114 let config = FormatterConfig::default();
115 let formatter = CodeFormatter::new(config);
116 assert_eq!(formatter.config().indent_width, 4);
117 }
118
119 #[tokio::test]
120 async fn test_format_simple_content() {
121 let config = FormatterConfig::default();
122 let formatter = CodeFormatter::new(config);
123
124 let input = "graph test{node a}";
125 let result = formatter.format_content(input).await.unwrap();
126
127 assert!(!result.is_empty());
129 }
130}