tui_explorer/
lib.rs

1use fluent_templates::{static_loader, Loader};
2use std::env;
3use std::ffi::OsString;
4use std::fs;
5use std::io::{self, Write};
6use std::path::{Path, PathBuf};
7use std::process::Command;
8use std::process::ExitStatus;
9use std::usize;
10use unic_langid::{langid, LanguageIdentifier};
11
12use input::*;
13
14mod input;
15
16static_loader! {
17    static LOCALES = {
18        locales: "locales",
19        fallback_language: "en-US",
20        // Removes unicode isolating marks around arguments, you typically
21        // should only set to false when testing.
22        customise: |bundle| bundle.set_use_isolating(true),
23    };
24}
25
26pub struct Config {
27    list_all: bool,
28    language: LanguageIdentifier,
29}
30impl Config {
31    /// reads the config and returns an instance of Config
32    pub fn read_conf() -> Self {
33        // Reading list_all
34        let as_u8: u8 = env::var("LIST_ALL")
35            .unwrap_or_else(|_err| "0".to_string())
36            .trim()
37            .parse()
38            .unwrap_or(0);
39        let mut list_all: bool = match as_u8 {
40            0 => false,
41            _ => true,
42        };
43
44        if env::args_os().nth(1).unwrap_or(OsString::from("0")) == *"--list-all" {
45            list_all = true;
46        };
47
48        // Reading language
49        let mut as_string = String::from("");
50        // Cuts up Unicode Identifier e.g en_US.UTF-8
51        for i in env::var("LANG").unwrap_or("en_US".into()).chars() {
52            if i == '.' {
53                break;
54            }
55            as_string += &String::from(i);
56        }
57        let language = as_string.parse().unwrap_or_else(|err| {
58            eprintln!("{}", err);
59            langid!("en_US")
60        });
61
62        Self { list_all, language }
63    }
64}
65
66/// opens a file
67/// # Errors
68/// Command::status returns an error
69/// # Panics
70/// Any Errors while getting input
71fn open_file(file: &Path, lang: &LanguageIdentifier) -> io::Result<ExitStatus> {
72    print!("{}", LOCALES.lookup(lang, "ask-for-program"));
73    io::stdout().flush().unwrap();
74    let mut input = String::new();
75    io::stdin()
76        .read_line(&mut input)
77        .unwrap_or_else(|_| panic!("{}", LOCALES.lookup(lang, "input-error")));
78
79    Command::new(input.trim()).arg(file).status()
80}
81
82/// prints out all directorys and returns the total size of elements
83/// # Panics
84/// an element is not valid unicode
85fn print_dirs(paths: &Vec<PathBuf>) -> usize {
86    println!("0: ..");
87    let mut i: usize = 0;
88    for path in paths {
89        i += 1;
90        println!("{}. {}", i, path.file_name().unwrap().to_str().unwrap());
91    }
92    i
93}
94
95/// filters out all elemts that start with '.'
96/// # Panics
97/// an element is not valid unicode
98fn filter_elements(elements: Vec<PathBuf>) -> Vec<PathBuf> {
99    let mut result: Vec<PathBuf> = vec![];
100    for i in elements {
101        if !i.file_name().unwrap().to_str().unwrap().starts_with(".") {
102            result.push(i);
103        }
104    }
105    result
106}
107
108/// rekursive function, that copies a whole directory
109fn copy_dir(from: &Path, to: &Path, lang: &LanguageIdentifier) {
110    if from.is_dir() {
111        if !to.exists() {
112            fs::create_dir_all(to).unwrap_or_else(|err| handle_err(err, lang));
113        }
114        let paths = match fs::read_dir(from) {
115            Ok(paths) => paths,
116            Err(err) => {
117                handle_err(err, lang);
118                return;
119            }
120        };
121        for dir in paths {
122            copy_dir(
123                dir.as_ref().unwrap().path().as_path(),
124                &to.join(dir.unwrap().file_name()),
125                lang,
126            );
127        }
128    } else {
129        fs::copy(from, to).unwrap_or_else(|err| {
130            eprintln!("{err}");
131            0
132        });
133    }
134}
135
136/// takes io::Error and prints a specific Message to stderr
137fn handle_err(err: io::Error, lang: &LanguageIdentifier) {
138    eprintln!(
139        "{}",
140        match err.kind() {
141            io::ErrorKind::NotFound => LOCALES.lookup(lang, "err-not-found"),
142            io::ErrorKind::PermissionDenied => LOCALES.lookup(lang, "err-permission-denied"),
143            io::ErrorKind::AlreadyExists => LOCALES.lookup(lang, "err-already-exists"),
144            _ => err.kind().to_string(),
145        }
146    )
147}
148
149/// runs the programm
150pub fn run(config: Config) -> Result<(), String> {
151    let mut list_all_once = false;
152    loop {
153        let dir = env::current_dir().unwrap();
154        let mut dir = dir.as_path();
155
156        let mut paths = dir
157            .read_dir()
158            .unwrap()
159            .map(|res| res.map(|e| e.path()))
160            .collect::<Result<Vec<_>, io::Error>>()
161            .unwrap();
162
163        if !config.list_all && !list_all_once {
164            paths = filter_elements(paths);
165        }
166        list_all_once = false;
167
168        println!("\n{}:", dir.to_str().unwrap());
169        let paths_num = print_dirs(&paths);
170
171        let input = match Input::get_input(&config.language) {
172            Ok(input) => match input {
173                Input::Choose(size) => {
174                    if size > paths_num {
175                        eprintln!("{}", LOCALES.lookup(&config.language, "no-valid-option"));
176                        continue;
177                    }
178                    size
179                }
180                Input::DirName(name) => {
181                    if Path::new(&name.trim()).is_dir() {
182                        env::set_current_dir(name.trim())
183                            .unwrap_or_else(|err| handle_err(err, &config.language));
184                    } else {
185                        println!(
186                            "{}",
187                            if let Err(err) = open_file(Path::new(&name), &config.language) {
188                                if err.kind() == io::ErrorKind::NotFound {
189                                    LOCALES.lookup(&config.language, "program-doesnt-exist")
190                                } else {
191                                    err.kind().to_string()
192                                }
193                            } else {
194                                String::from("")
195                            }
196                        );
197                    }
198                    continue;
199                }
200                Input::Copy(from, to) => {
201                    copy_dir(Path::new(&from), Path::new(&to), &config.language);
202                    continue;
203                }
204                Input::NewDir(name) => {
205                    fs::create_dir(name).unwrap_or_else(|err| handle_err(err, &config.language));
206                    continue;
207                }
208                Input::Rm(name) => {
209                    let as_path = Path::new(name.trim());
210                    if as_path.is_dir() {
211                        fs::remove_dir_all(name.trim())
212                            .unwrap_or_else(|err| handle_err(err, &config.language));
213                    } else {
214                        fs::remove_file(name.trim())
215                            .unwrap_or_else(|err| handle_err(err, &config.language));
216                    }
217                    continue;
218                }
219                Input::Command(cmd) => {
220                    if let Err(err) = Command::new("sh").arg("-c").arg(cmd).status() {
221                        handle_err(err, &config.language);
222                    }
223                    continue;
224                }
225                Input::ListAll => {
226                    list_all_once = true;
227                    continue;
228                }
229                Input::Quit => return Ok(()),
230            },
231            Err(string) => {
232                println!("{string}");
233                continue;
234            }
235        };
236
237        dir = if input == 0 {
238            if let Some(path) = dir.parent() {
239                path
240            } else {
241                eprintln!("{}", LOCALES.lookup(&config.language, "no-root-dir"));
242                continue;
243            }
244        } else {
245            Path::new(&paths[input - 1])
246        };
247        if dir.is_file() {
248            println!(
249                "{}",
250                if let Err(err) = open_file(dir, &config.language) {
251                    if err.kind() == io::ErrorKind::NotFound {
252                        LOCALES.lookup(&config.language, "program-doesnt-exist")
253                    } else {
254                        err.kind().to_string()
255                    }
256                } else {
257                    String::from("")
258                }
259            );
260            continue;
261        }
262        env::set_current_dir(dir).unwrap_or_else(|err| handle_err(err, &config.language));
263    }
264}