diff --git a/.gitignore b/.gitignore index 7a00c3f..5acf9c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Generated by Cargo # will have compiled files and executables -debug/ -target/ +debug/** +target/** # These are backup files generated by rustfmt **/*.rs.bk @@ -9,12 +9,8 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - # Build - Test: -*.txt \ No newline at end of file +*.txt + +# Releases (realAscot) +releases/** diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..7e65fe4 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,32 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "cargo", + "command": "check", + "problemMatcher": [ + "$rustc" + ], + "group": "build", + "label": "rust: cargo check" + }, + { + "type": "cargo", + "command": "clean", + "problemMatcher": [ + "$rustc" + ], + "group": "clean", + "label": "rust: cargo clean" + }, + { + "type": "cargo", + "command": "build", + "problemMatcher": [ + "$rustc" + ], + "group": "build", + "label": "rust: cargo build" + } + ] +} \ No newline at end of file diff --git a/src/config/args.rs b/src/config/args.rs index 0ae9a3a..831bafb 100644 --- a/src/config/args.rs +++ b/src/config/args.rs @@ -10,6 +10,14 @@ use std::path::PathBuf; about = "Generiert eine ASCII-Baumstruktur eines Verzeichnisses." )] pub struct CliArgs { + /// Aktiviere den Debug-Modus + #[clap(short = 'D', long = "debug", global = true, action)] + pub debug: bool, + + /// Aktiviere den Silent-Mode für Verwendung in Batch und Skripten + #[clap(short = 'q', long, global = true, action)] + pub quiet: bool, + /// Stammverzeichnis (default: aktuelles Verzeichnis) #[arg(default_value = ".")] pub root_path: PathBuf, diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..cada94e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +pub mod config; +pub mod app; +pub mod utils; diff --git a/src/main.rs b/src/main.rs index 9e7de4d..fd1db1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,11 +6,16 @@ use config::args::CliArgs; use config::loader::load_config_from_home; use clap::Parser; use std::fs; +use std::time::Instant; + +use treescanner::utils::ascii_spinner::start_spinner; fn main() { let args = CliArgs::parse(); let file_config = load_config_from_home().unwrap_or_default(); + let start_time = Instant::now(); + if !args.root_path.is_dir() { eprintln!("Fehler: '{}' ist kein gültiges Verzeichnis.", args.root_path.display()); std::process::exit(1); @@ -41,8 +46,21 @@ fn main() { }; let mut builder = TreeBuilder::new(config); + + // Spinner starten, wenn nicht quiet + let spinner = if !args.quiet { + Some(start_spinner(2)) + } else { + None + }; + let output = builder.build_tree(); + if let Some(stop) = spinner { + let _ = stop.send(()); + } + println!(); // saubere Zeile nach Spinner + let viewonly = args.viewonly || file_config.viewonly.unwrap_or(false); let output_path = args.output.clone().or_else(|| file_config.output.map(Into::into)).unwrap_or_else(|| "tree.txt".into()); @@ -51,13 +69,16 @@ fn main() { eprintln!("Fehler beim Schreiben der Datei: {e}"); std::process::exit(1); } - let (folders, files) = builder.stats(); - println!( - "Erfasst wurden {} Ordner und {} Dateien. Ergebnis in {} gespeichert.", - folders, - files, - output_path.display() - ); + if !args.quiet { + let (folders, files) = builder.stats(); + println!( + "Erfasst wurden {} Ordner und {} Dateien. Ergebnis in {} gespeichert.", + folders, + files, + output_path.display() + ); + println!("⏱️ Gesamtlaufzeit: {:.2?}", start_time.elapsed()); + } } else { println!("{}", output); } diff --git a/src/utils/ascii_spinner.rs b/src/utils/ascii_spinner.rs new file mode 100644 index 0000000..cfb41cf --- /dev/null +++ b/src/utils/ascii_spinner.rs @@ -0,0 +1,35 @@ +use std::sync::mpsc::{self, Sender}; +use std::thread; +use std::time::Duration; + +/// Startet einen minimalistischen ASCII-Spinner in einem Hintergrundthread. +/// +/// Gibt alle X Millisekunden einen neuen Frame auf stdout aus (mit `\r`). +/// Die Frequenz wird über `ticks_per_second` gesteuert. +/// +/// # Beispiel +/// ``` +/// let stop = ascii_spinner::start_spinner(8); // 8 Ticks pro Sekunde +/// // ... lange Operation ... +/// let _ = stop.send(()); +/// ``` +pub fn start_spinner(ticks_per_second: u64) -> Sender<()> { + let (tx, rx) = mpsc::channel(); + let frames = vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + let clamped = ticks_per_second.clamp(1, 20); + let interval = Duration::from_millis(1000 / clamped); + + thread::spawn(move || { + let mut idx = 0; + while rx.try_recv().is_err() { + print!("\r[{}] läuft ...", frames[idx % frames.len()]); + let _ = std::io::Write::flush(&mut std::io::stdout()); + idx += 1; + thread::sleep(interval); + } + print!("\r \r"); // Spinner löschen + let _ = std::io::Write::flush(&mut std::io::stdout()); + }); + + tx +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index d991728..a15be27 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,2 @@ pub mod logger; +pub mod ascii_spinner; diff --git a/tests/config_tests.rs b/tests/config_tests.rs new file mode 100644 index 0000000..ce7996e --- /dev/null +++ b/tests/config_tests.rs @@ -0,0 +1,29 @@ +use treescanner::config::loader::{load_config_from_home, ConfigFile}; +use std::fs; +use std::path::PathBuf; + +fn write_temp_config(content: &str) -> PathBuf { + let path = dirs::home_dir().unwrap().join(".treescanner.conf"); + fs::write(&path, content).expect("Konnte Testdatei nicht schreiben"); + path +} + +#[test] +fn test_config_wird_korrekt_geladen() { + let config_text = r#" + max_depth = 2 + max_files_per_dir = 5 + ignore = [".git", "target"] + viewonly = true + "#; + + let conf_path = write_temp_config(config_text); + let loaded: ConfigFile = load_config_from_home().expect("Konfig konnte nicht geladen werden"); + + assert_eq!(loaded.max_depth, Some(2)); + assert_eq!(loaded.max_files_per_dir, Some(5)); + assert!(loaded.ignore.as_ref().unwrap().contains(".git")); + assert_eq!(loaded.viewonly, Some(true)); + + fs::remove_file(conf_path).ok(); +}