sane_fmt/app/
run.rs

1use super::super::{
2    act,
3    cli_opt::{LogFormat, When},
4    cross_platform_path, file_list,
5    term::color::{ColorScheme, ColorfulScheme, ColorlessScheme},
6};
7use super::App;
8use pipe_trait::*;
9use relative_path::RelativePath;
10use std::{
11    env, fs,
12    io::{stdin, Read, Write},
13    path::{Path, PathBuf, MAIN_SEPARATOR},
14};
15use tap::tap::*;
16
17impl App {
18    /// Run the program based on application state.
19    pub fn run(&self) -> Result<(), String> {
20        let Self { opt, fmt } = self;
21
22        if opt.stdio {
23            let mut buffer = String::new();
24            stdin()
25                .read_to_string(&mut buffer)
26                .map_err(|error| format!("Failed to read from STDIN: {}", error))?;
27            let formatted = fmt
28                .format_text(&PathBuf::from("STDIN.ts"), &buffer)?
29                .unwrap_or(buffer);
30            print!("{}", formatted);
31            return Ok(());
32        }
33
34        let files = if opt.files.is_empty() && opt.include.is_none() {
35            file_list::default_files().map_err(|error| error.to_string())?
36        } else {
37            let files = opt
38                .files
39                .iter()
40                .map(|x| cross_platform_path::from_string(x.as_str(), MAIN_SEPARATOR))
41                .pipe(file_list::create_list)
42                .map_err(|error| error.to_string())?;
43            if let Some(list_file_address) = &opt.include {
44                list_file_address
45                    .pipe(file_list::read_list)
46                    .map_err(|error| error.to_string())?
47                    .tap_mut(|x| x.extend(files))
48            } else {
49                files
50            }
51        };
52
53        let file_count = files.len();
54        let mut diff_count = 0;
55
56        let theme: &dyn ColorScheme = if opt.color == When::Never {
57            &ColorlessScheme
58        } else {
59            &ColorfulScheme
60        };
61
62        let log_same = act::log_same::get(opt.details, opt.hide_passed, theme);
63        let log_diff = act::log_diff::get(opt.details, opt.log_format, theme);
64        let may_write = act::may_write::get(opt.write);
65
66        for item in files {
67            let file_list::Item { path, .. } = item;
68
69            // Problem: RelativePath panics on absolute path
70            // Workaround: Only use RelativePath on relative path
71            let path = if path.is_absolute() {
72                path
73            } else {
74                // Problem: RelativePath only recognize unix path separator
75                // Workaround: Always use unix path separator
76                let path = if cfg!(unix) {
77                    path
78                } else {
79                    // This is an expensive operation, therefore should only be performed when necessary
80                    cross_platform_path::convert_path(&path, '/')
81                };
82
83                let path = path
84                    .pipe_ref(RelativePath::from_path)
85                    .unwrap()
86                    .normalize()
87                    .to_string();
88
89                let path: &Path = if path.starts_with("./") || path.starts_with(".\\") {
90                    &path[2..]
91                } else {
92                    &path
93                }
94                .as_ref();
95
96                // Because of the above workaround, this is necessary
97                if cfg!(unix) {
98                    path.to_path_buf()
99                } else {
100                    cross_platform_path::convert_path(path, MAIN_SEPARATOR)
101                }
102            };
103
104            let path = &path;
105            let file_content = fs::read_to_string(path).map_err(|error| {
106                format!(
107                    "Failed to read {path:?}: {error}",
108                    path = cross_platform_path::to_string(path, '/'),
109                    error = error,
110                )
111            })?;
112
113            let formatted = fmt.format_text(path, &file_content).map_err(|error| {
114                format!(
115                    "Failed to parse {path:?}: {error}",
116                    path = cross_platform_path::to_string(path, '/'),
117                    error = error,
118                )
119            })?;
120            if let Some(formatted) = formatted {
121                assert_ne!(file_content, formatted);
122                diff_count += 1;
123                log_diff(path, &file_content, &formatted);
124                may_write(path, &formatted).map_err(|error| {
125                    format!(
126                        "Failed to write to {path:?}: {error}",
127                        path = cross_platform_path::to_string(path, '/'),
128                        error = error,
129                    )
130                })?;
131            } else {
132                log_same(path);
133            }
134        }
135
136        println!(
137            "SUMMARY: total {}; changed {}; unchanged {}",
138            file_count,
139            diff_count,
140            file_count - diff_count,
141        );
142
143        if opt.log_format == LogFormat::GitHubActions {
144            if let Some(gh_output_file) = env::var_os("GITHUB_OUTPUT") {
145                let mut gh_output_file = fs::OpenOptions::new()
146                    .append(true)
147                    .create(true)
148                    .open(gh_output_file)
149                    .unwrap();
150                writeln!(gh_output_file, "total={}", file_count).unwrap();
151                writeln!(gh_output_file, "changed={}", diff_count).unwrap();
152                writeln!(gh_output_file, "unchanged={}", file_count - diff_count).unwrap();
153            } else {
154                println!("::set-output name=total::{}", file_count);
155                println!("::set-output name=changed::{}", diff_count);
156                println!("::set-output name=unchanged::{}", file_count - diff_count);
157            }
158        }
159
160        if file_count == 0 {
161            return Err("No files found".to_string());
162        }
163
164        if !opt.write && diff_count != 0 {
165            return Err(format!("There are {} unformatted files", diff_count));
166        }
167
168        Ok(())
169    }
170}