Skip to main content

cargo_plot/core/
save.rs

1use super::super::i18n::I18n;
2use crate::theme::for_path_tree::get_file_type;
3use std::fs;
4use std::path::Path;
5
6pub struct SaveFile;
7
8impl SaveFile {
9    // ⚡ Nowa funkcja tabelarycznej stopki
10    pub fn generate_by_section(tag: &str, enter_path: &str, i18n: &I18n, cmd: &str) -> String {
11        let mut f = String::new();
12        f.push_str("\n\n---\n\n");
13        f.push_str("> | Property | Value |\n");
14        f.push_str("> | ---: | :--- |\n");
15        f.push_str(&format!(
16            "> | **{}** | `cargo-plot v0.2.0` |\n",
17            i18n.footer_tool()
18        ));
19        f.push_str(&format!(
20            "> | **{}** | `{}` |\n",
21            i18n.footer_input(),
22            enter_path
23        ));
24        f.push_str(&format!("> | **{}** | `{}` |\n", i18n.footer_cmd(), cmd));
25        f.push_str(&format!("> | **{}** | `{}` |\n", i18n.footer_tag(), tag));
26
27        let links = "[Crates.io](https://crates.io/crates/cargo-plot) \\| [GitHub](https://github.com/j-Cis/cargo-plot/releases)";
28        f.push_str(&format!("> | **{}** | {} |\n", i18n.footer_links(), links));
29        f.push_str(&format!(
30            "> | **{}** | `cargo install cargo-plot` |\n",
31            i18n.footer_links()
32        ));
33        f.push_str(&format!(
34            "> | **{}** | `cargo plot --help` |\n",
35            i18n.footer_help()
36        ));
37        f.push_str("\n---\n");
38        f
39    }
40
41    /// Wspólna logika zapisu do pliku (DRY): tworzenie folderów i zapis IO.
42    fn write_to_disk(filepath: &str, content: &str, log_name: &str, i18n: &I18n) {
43        let path = Path::new(filepath);
44
45        if let Some(parent) = path.parent()
46            && !parent.as_os_str().is_empty()
47            && !parent.exists()
48            && let Err(e) = fs::create_dir_all(parent)
49        {
50            eprintln!(
51                "{}",
52                i18n.dir_create_err(&parent.to_string_lossy(), &e.to_string())
53            );
54            return;
55        }
56
57        match fs::write(path, content) {
58            Ok(_) => println!("{}", i18n.save_success(log_name, filepath)),
59            Err(e) => eprintln!("{}", i18n.save_err(log_name, filepath, &e.to_string())),
60        }
61    }
62
63    /// Formatowanie i zapis samego widoku struktury (ścieżek)
64    pub fn paths(
65        content: &str,
66        filepath: &str,
67        tag: &str,
68        add_by: bool,
69        i18n: &I18n,
70        cmd: &str,
71        enter_path: &str,
72    ) {
73        let by_section = if add_by {
74            Self::generate_by_section(tag, enter_path, i18n, cmd)
75        } else {
76            String::new()
77        };
78        let internal_tag = if add_by { "" } else { tag };
79        let file_name = Path::new(filepath)
80            .file_name()
81            .unwrap_or_default()
82            .to_string_lossy();
83
84        let markdown_content = format!(
85            "# {}\n\n```plaintext\n{}\n```\n\n{}{}",
86            file_name, content, internal_tag, by_section
87        );
88
89        Self::write_to_disk(
90            filepath,
91            &markdown_content,
92            if i18n.lang == crate::i18n::Lang::Pl {
93                "ścieżki"
94            } else {
95                "paths"
96            },
97            i18n,
98        );
99    }
100
101    /// Formatowanie i zapis pełnego cache (drzewo + zawartość plików)
102    pub fn codes(
103        tree_text: &str,
104        paths: &[String],
105        base_dir: &str,
106        filepath: &str,
107        tag: &str,
108        add_by: bool,
109        i18n: &I18n,
110        cmd: &str,
111        enter_path: &str,
112    ) {
113        let by_section = if add_by {
114            Self::generate_by_section(tag, enter_path, i18n, cmd)
115        } else {
116            String::new()
117        };
118        let internal_tag = if add_by { "" } else { tag };
119        let file_name = Path::new(filepath)
120            .file_name()
121            .unwrap_or_default()
122            .to_string_lossy();
123
124        let mut content = String::new();
125        content.push_str(&format!("# {}\n\n", file_name));
126
127        // Wstawiamy wygenerowane drzewo ścieżek
128        content.push_str("```plaintext\n");
129        content.push_str(tree_text);
130        content.push_str("```\n\n");
131
132        let mut counter = 1;
133
134        for p_str in paths {
135            if p_str.ends_with('/') {
136                continue; // Pomijamy katalogi
137            }
138
139            let absolute_path = Path::new(base_dir).join(p_str);
140            let ext = absolute_path
141                .extension()
142                .unwrap_or_default()
143                .to_string_lossy()
144                .to_lowercase();
145
146            let lang = get_file_type(&ext).md_lang;
147
148            if is_blacklisted_extension(&ext) {
149                content.push_str(&format!(
150                    "### {:03}: `{}`\n\n{}\n\n",
151                    counter,
152                    p_str,
153                    i18n.skip_binary()
154                ));
155                counter += 1;
156                continue;
157            }
158
159            match fs::read_to_string(&absolute_path) {
160                Ok(file_content) => {
161                    content.push_str(&format!(
162                        "### {:03}: `{}`\n\n```{}\n{}\n```\n\n",
163                        counter, p_str, lang, file_content
164                    ));
165                }
166                Err(_) => {
167                    content.push_str(&format!(
168                        "### {:03}: `{}`\n\n{}\n\n",
169                        counter,
170                        p_str,
171                        i18n.read_err()
172                    ));
173                }
174            }
175            counter += 1;
176        }
177
178        content.push_str(&format!("\n\n{}{}", internal_tag, by_section));
179        Self::write_to_disk(
180            filepath,
181            &content,
182            if i18n.lang == crate::i18n::Lang::Pl {
183                "kod (cache)"
184            } else {
185                "code (cache)"
186            },
187            i18n,
188        );
189    }
190}
191
192// [EN]: Security mechanisms to prevent processing non-text or binary files.
193// [PL]: Mechanizmy bezpieczeństwa zapobiegające przetwarzaniu plików nietekstowych lub binarnych.
194
195/// [EN]: Checks if a file extension is on the list of forbidden binary types.
196/// [PL]: Sprawdza, czy rozszerzenie pliku znajduje się na liście zabronionych typów binarnych.
197pub fn is_blacklisted_extension(ext: &str) -> bool {
198    let e = ext.to_lowercase();
199
200    matches!(
201        e.as_str(),
202        // --------------------------------------------------
203        // GRAFIKA I DESIGN
204        // --------------------------------------------------
205        "png" | "jpg" | "jpeg" | "gif" | "bmp" | "ico" | "svg" | "webp" | "tiff" | "tif" | "heic" | "psd" | 
206        "ai" | 
207        // --------------------------------------------------
208        // BINARKI | BIBLIOTEKI I ARTEFAKTY KOMPILACJI
209        // --------------------------------------------------
210        "exe" | "dll" | "so" | "dylib" | "bin" | "wasm" | "pdb" | "rlib" | "rmeta" | "lib" | 
211        "o" | "a" | "obj" | "pch" | "ilk" | "exp" | 
212        "jar" | "class" | "war" | "ear" | 
213        "pyc" | "pyd" | "pyo" | "whl" | 
214        // --------------------------------------------------
215        // ARCHIWA I PACZKI
216        // --------------------------------------------------
217        "zip" | "tar" | "gz" | "tgz" | "7z" | "rar" | "bz2" | "xz" | "iso" | "dmg" | "pkg" | "apk" | 
218        // --------------------------------------------------
219        // DOKUMENTY | BAZY DANYCH I FONTY
220        // --------------------------------------------------
221        "sqlite" | "sqlite3" | "db" | "db3" | "mdf" | "ldf" | "rdb" | 
222        "pdf" | "doc" | "docx" | "xls" | "xlsx" | "ppt" | "pptx" | "odt" | "ods" | "odp" | 
223        "woff" | "woff2" | "ttf" | "eot" | "otf" | 
224        // --------------------------------------------------
225        // MEDIA (AUDIO / WIDEO)
226        // --------------------------------------------------
227        "mp3" | "mp4" | "avi" | "mkv" | "wav" | "flac" | "ogg" | "m4a" | "mov" | "wmv" | "flv"
228    )
229}