1#![warn(missing_docs, clippy::unwrap_used)]
4
5pub mod app;
7pub mod args;
9pub mod command;
11pub mod error;
13pub mod event;
15pub mod options;
17pub mod style;
19pub mod ui;
21pub 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
41pub 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 == §ion.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}