systeroid_tui/
lib.rs

1//! A terminal user interface for managing kernel parameters.
2
3#![warn(missing_docs, clippy::unwrap_used)]
4
5/// Main application.
6pub mod app;
7/// Command-line argument parser.
8pub mod args;
9/// Application commands.
10pub mod command;
11/// Error implementation.
12pub mod error;
13/// Event handling.
14pub mod event;
15/// Application options.
16pub mod options;
17/// Style helper.
18pub mod style;
19/// User interface renderer.
20pub mod ui;
21/// Custom widgets.
22pub mod widgets;
23
24use crate::app::App;
25use crate::args::Args;
26use crate::command::Command;
27use crate::error::Result;
28use crate::event::{Event, EventHandler};
29use crate::style::Colors;
30use command::LoggerCommand;
31use log::LevelFilter;
32use ratatui::backend::Backend;
33use ratatui::Terminal;
34use std::env;
35use std::str::FromStr;
36use systeroid_core::cache::Cache;
37use systeroid_core::config::Config;
38use systeroid_core::sysctl::controller::Sysctl;
39use tui_logger::TuiLoggerFile;
40
41/// Runs `systeroid-tui`.
42pub fn run<B: Backend>(args: Args, backend: B) -> Result<()> {
43    let mut config = Config {
44        display_deprecated: args.display_deprecated,
45        kernel_docs: args.kernel_docs,
46        ..Default::default()
47    };
48    config.tui.tick_rate = args.tick_rate;
49    config.tui.save_path = args.save_path;
50    config.tui.log_file = args.log_file;
51    config.tui.no_docs = args.no_docs;
52    config.tui.color.fg_color = args.fg_color;
53    config.tui.color.bg_color = args.bg_color;
54    config.parse(args.config)?;
55    let colors = Colors::new(&config.tui.color.bg_color, &config.tui.color.fg_color)?;
56    tui_logger::init_logger(if let Ok(log_level) = env::var("RUST_LOG") {
57        LevelFilter::from_str(&log_level)?
58    } else {
59        LevelFilter::Trace
60    })?;
61    tui_logger::set_default_level(LevelFilter::Trace);
62    if let Some(ref log_file) = config.tui.log_file {
63        let file_options = TuiLoggerFile::new(log_file);
64        tui_logger::set_log_file(file_options);
65    }
66    log::trace!(target: "config", "{:?}", config);
67    let mut sysctl = Sysctl::init(config)?;
68    if !sysctl.config.tui.no_docs {
69        sysctl.update_docs_from_cache(&Cache::init()?)?;
70    }
71    let mut terminal = Terminal::new(backend)?;
72    terminal.hide_cursor()?;
73    terminal.clear()?;
74    let event_handler = EventHandler::new(sysctl.config.tui.tick_rate);
75    let mut app = App::new(&mut sysctl);
76    if let Some(section) = args.section {
77        app.section_list.state.select(Some(
78            app.section_list
79                .items
80                .iter()
81                .position(|r| r == &section.to_string())
82                .unwrap_or(0),
83        ));
84        app.search();
85    }
86    if args.search_query.is_some() {
87        app.input = args.search_query;
88        app.search();
89        app.input = None;
90    }
91    while app.running {
92        terminal.draw(|frame| ui::render(frame, &mut app, &colors))?;
93        match event_handler.next()? {
94            Event::KeyPress(key) => {
95                let mut command = Command::parse(key, app.is_input_mode());
96                if app.show_logs {
97                    command = LoggerCommand::parse(key)
98                        .map(Command::LoggerEvent)
99                        .unwrap_or(command);
100                }
101                app.run_command(command)?;
102            }
103            #[cfg(not(test))]
104            Event::Tick => {
105                app.tick();
106            }
107            #[cfg(test)]
108            Event::Tick => {
109                app.running = false;
110            }
111        }
112    }
113    Ok(())
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use ratatui::backend::TestBackend;
120
121    #[test]
122    fn test_systeroid_tui() -> Result<()> {
123        let args = Args {
124            tick_rate: 1000,
125            fg_color: String::from("white"),
126            bg_color: String::from("black"),
127            ..Args::default()
128        };
129        let backend = TestBackend::new(40, 10);
130        run(args, backend)?;
131        Ok(())
132    }
133}