Skip to main content

rusty_stack/
lib.rs

1//! Rusty-Stack Library
2//!
3//! This library contains the core TUI installer, benchmark infrastructure,
4//! and the new platform modules (core, platform, orchestrator, telemetry,
5//! adapter).
6
7pub mod adapter;
8pub mod benchmark_logs;
9pub mod benchmark_runners;
10pub mod benchmarks;
11pub mod bootstrap;
12pub mod component_status;
13pub mod config;
14pub mod core;
15pub mod hardware;
16pub mod installer;
17pub mod installers;
18pub mod logging;
19pub mod orchestrator;
20pub mod platform;
21pub mod state;
22pub mod telemetry;
23pub mod verification;
24
25#[cfg(feature = "tui")]
26pub mod app;
27
28#[cfg(feature = "tui")]
29pub mod widgets;
30
31use std::path::PathBuf;
32
33/// Detect the scripts directory by checking current dir and parent dir.
34pub fn detect_scripts_dir() -> String {
35    let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
36    let scripts = cwd.join("scripts");
37    if scripts.exists() {
38        return scripts.to_string_lossy().to_string();
39    }
40    let parent_scripts = cwd.join("..").join("scripts");
41    if parent_scripts.exists() {
42        return parent_scripts.to_string_lossy().to_string();
43    }
44    "./scripts".to_string()
45}
46
47/// Set the MLSTACK_REPO_ROOT environment variable based on scripts dir location.
48pub fn set_repo_root(scripts_dir: &str) {
49    let scripts_path = PathBuf::from(scripts_dir);
50    if let Some(root) = scripts_path.parent() {
51        if root.join("stans_ml_stack").exists() {
52            std::env::set_var("MLSTACK_REPO_ROOT", root);
53        }
54    }
55}
56
57/// Run the TUI application.
58///
59/// This function encapsulates the full TUI lifecycle: panic hook setup,
60/// raw mode, alternate screen, terminal creation, app initialization,
61/// event loop, and cleanup.
62#[cfg(feature = "tui")]
63pub fn run_tui() -> anyhow::Result<()> {
64    use crate::app::App;
65    use crossterm::execute;
66    use crossterm::terminal::{
67        disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
68    };
69    use ratatui::backend::CrosstermBackend;
70    use ratatui::Terminal;
71    use std::io;
72    use std::time::{Duration, Instant};
73
74    std::panic::set_hook(Box::new(|info| {
75        let _ = disable_raw_mode();
76        let mut stdout = io::stdout();
77        let _ = execute!(stdout, LeaveAlternateScreen);
78        eprintln!("Rusty-Stack crashed: {info}");
79    }));
80
81    enable_raw_mode()?;
82    let mut stdout = io::stdout();
83    let use_alt_screen = std::env::var("MLSTACK_NO_ALT_SCREEN").is_err();
84    if use_alt_screen {
85        execute!(stdout, EnterAlternateScreen)?;
86    }
87    let backend = CrosstermBackend::new(stdout);
88    let mut terminal = Terminal::new(backend)?;
89
90    let scripts_dir = detect_scripts_dir();
91    set_repo_root(&scripts_dir);
92    let mut app = App::new(scripts_dir);
93
94    let tick_rate = Duration::from_millis(100);
95    let mut last_tick = Instant::now();
96
97    let res = run_app(&mut terminal, &mut app, tick_rate, &mut last_tick);
98
99    disable_raw_mode()?;
100    if use_alt_screen {
101        execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
102    }
103    terminal.show_cursor()?;
104
105    if let Err(err) = res {
106        eprintln!("Error: {err:?}");
107    }
108
109    Ok(())
110}
111
112/// Internal event loop for the TUI application.
113#[cfg(feature = "tui")]
114fn run_app<B: ratatui::backend::Backend>(
115    terminal: &mut ratatui::Terminal<B>,
116    app: &mut crate::app::App,
117    tick_rate: std::time::Duration,
118    last_tick: &mut std::time::Instant,
119) -> anyhow::Result<()>
120where
121    <B as ratatui::backend::Backend>::Error: Send + Sync + 'static,
122{
123    use crate::state::Stage;
124    use crossterm::event::{self, Event, KeyCode, KeyModifiers};
125
126    loop {
127        terminal.draw(|frame| app.draw(frame))?;
128
129        let timeout = tick_rate
130            .checked_sub(last_tick.elapsed())
131            .unwrap_or_else(|| std::time::Duration::from_secs(0));
132
133        if event::poll(timeout)? {
134            match event::read()? {
135                Event::Key(key) => {
136                    if key.code == KeyCode::Char('c')
137                        && key.modifiers.contains(KeyModifiers::CONTROL)
138                    {
139                        return Ok(());
140                    }
141                    if key.code == KeyCode::Char('q') && app.stage == Stage::Recovery {
142                        return Ok(());
143                    }
144                    app.handle_key(key);
145                }
146                Event::Resize(_, _) => {
147                    terminal.autoresize()?;
148                }
149                _ => {}
150            }
151        }
152
153        if last_tick.elapsed() >= tick_rate {
154            app.on_tick();
155            *last_tick = std::time::Instant::now();
156        }
157
158        if app.should_exit {
159            return Ok(());
160        }
161    }
162}