mago_service/formatter/
mod.rs

1use mago_feedback::create_progress_bar;
2use mago_feedback::remove_progress_bar;
3use mago_feedback::ProgressBarTheme;
4use mago_formatter::format;
5use mago_interner::ThreadedInterner;
6use mago_parser::parse_source;
7use mago_source::error::SourceError;
8use mago_source::SourceIdentifier;
9use mago_source::SourceManager;
10
11use crate::formatter::config::FormatterConfiguration;
12use crate::utils;
13
14pub mod config;
15
16#[derive(Debug)]
17pub struct FormatterService {
18    configuration: FormatterConfiguration,
19    interner: ThreadedInterner,
20    source_manager: SourceManager,
21}
22
23impl FormatterService {
24    pub fn new(
25        configuration: FormatterConfiguration,
26        interner: ThreadedInterner,
27        source_manager: SourceManager,
28    ) -> Self {
29        Self { configuration, interner, source_manager }
30    }
31
32    /// Runs the formatting process.
33    pub async fn run(&self, dry_run: bool) -> Result<usize, SourceError> {
34        // Process sources concurrently
35        self.process_sources(self.source_manager.user_defined_source_ids().collect(), dry_run).await
36    }
37
38    #[inline]
39    async fn process_sources(&self, source_ids: Vec<SourceIdentifier>, dry_run: bool) -> Result<usize, SourceError> {
40        let settings = self.configuration.get_settings();
41        let mut handles = Vec::with_capacity(source_ids.len());
42
43        let source_pb = create_progress_bar(source_ids.len(), "📂  Loading", ProgressBarTheme::Red);
44        let parse_pb = create_progress_bar(source_ids.len(), "🧩  Parsing", ProgressBarTheme::Blue);
45        let format_pb = create_progress_bar(source_ids.len(), "✨  Formatting", ProgressBarTheme::Magenta);
46        let write_pb = create_progress_bar(source_ids.len(), "🖊️  Writing", ProgressBarTheme::Green);
47
48        for source_id in source_ids.into_iter() {
49            handles.push(tokio::spawn({
50                let interner = self.interner.clone();
51                let manager = self.source_manager.clone();
52                let source_pb = source_pb.clone();
53                let parse_pb = parse_pb.clone();
54                let format_pb = format_pb.clone();
55                let write_pb = write_pb.clone();
56
57                async move {
58                    // Step 1: load the source
59                    let source = manager.load(&source_id)?;
60                    source_pb.inc(1);
61
62                    // Step 2: parse the source
63                    let (program, error) = parse_source(&interner, &source);
64                    parse_pb.inc(1);
65
66                    if let Some(error) = error {
67                        let source_name = interner.lookup(&source.identifier.0);
68                        mago_feedback::error!("skipping formatting for source '{}', {} ", source_name, error);
69
70                        format_pb.inc(1);
71                        write_pb.inc(1);
72
73                        return Result::<bool, SourceError>::Ok(false);
74                    }
75
76                    // Step 3: format the source
77                    let formatted = format(settings, &interner, &source, &program);
78                    format_pb.inc(1);
79
80                    // Step 4: write the formatted source
81                    let changed = utils::apply_changes(&interner, &manager, &source, formatted, dry_run)?;
82                    write_pb.inc(1);
83
84                    Result::<bool, SourceError>::Ok(changed)
85                }
86            }));
87        }
88
89        let mut changed = 0;
90        for handle in handles {
91            if handle.await.expect("failed to format files, this should never happen.")? {
92                changed += 1;
93            }
94        }
95
96        remove_progress_bar(source_pb);
97        remove_progress_bar(parse_pb);
98        remove_progress_bar(format_pb);
99        remove_progress_bar(write_pb);
100
101        Ok(changed)
102    }
103}