Skip to content

Instantly share code, notes, and snippets.

@RandyMcMillan
Last active March 12, 2025 21:20
Show Gist options
  • Save RandyMcMillan/0b350efcef0a5435bb0ea0d1f6ae0075 to your computer and use it in GitHub Desktop.
Save RandyMcMillan/0b350efcef0a5435bb0ea0d1f6ae0075 to your computer and use it in GitHub Desktop.
crossterm-lifecycle.rs
// Cargo.toml
// [package]
// name = "crossterm-lifecycle"
// version = "0.1.0"
// edition = "2021"
//
// [dependencies]
// tokio = { version = "1.44.0", features = ["full"] }
// ratatui = "0.26"
// crossterm = "0.27"
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;
use tokio::spawn;
use ratatui::{/*backend::Backend,*/ Terminal};
use std::{env, io};
use log::{debug, error, info, trace, warn};
async fn pre() -> Result<(), Box<dyn std::error::Error>> {
let mut child = Command::new("echo")
.arg("pre")
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()?;
let stdout = child.stdout.take().expect("stdout was not configured");
let stderr = child.stderr.take().expect("stderr was not configured");
let stdout_reader = BufReader::new(stdout);
let stderr_reader = BufReader::new(stderr);
let stdout_handle = spawn(async move {
let mut lines = stdout_reader.lines();
while let Some(line) = lines.next_line().await.unwrap_or(Some("0".to_string())) {
println!("{}", line);
}
});
let stderr_handle = spawn(async move {
let mut lines = stderr_reader.lines();
while let Some(line) = lines.next_line().await.unwrap_or(Some("0".to_string())) {
eprintln!("stderr: {}", line);
}
});
let _status = child.wait().await?;
stdout_handle.await?;
stderr_handle.await?;
//println!("Exited with status: {}", _status);
let option_handle = spawn(async move {
process_options(Some(10), Some("pre-hello".to_string())).await;
});
option_handle.await?;
let option_handle = spawn(async move {
process_options(Some(20), None).await;
});
option_handle.await?;
let option_handle = spawn(async move {
process_options(None, Some("pre-world".to_string())).await;
});
option_handle.await?;
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), io::Error> {
env_logger::init();
let mut args: Vec<String> = env::args().collect();
let mut _pre_count = 0;
let mut _mid_count = 0;
let mut _post_count = 0;
let mut output_count = 0;
let mut output_files: Vec<String> = Vec::new();
let mut verbose_count = 0;
// Simulating command-line arguments (for testing)
#[cfg(debug_assertions)]
args.push("-v".to_string());
#[cfg(debug_assertions)]
args.push("-v".to_string());
#[cfg(debug_assertions)]
args.push("-o".to_string());
#[cfg(debug_assertions)]
args.push("test1.txt".to_string());
#[cfg(debug_assertions)]
args.push("-o".to_string());
#[cfg(debug_assertions)]
args.push("test2.txt".to_string());
#[cfg(debug_assertions)]
args.push("extra_arg".to_string());
let mut i = 1; // Start from 1 to skip the executable path
while i < args.len() {
match args[i].as_str() {
"-1" | "--pre" => {
_pre_count += 1;
}
"-2" | "--mid" => {
_mid_count += 1;
}
"-3" | "--post" => {
_post_count += 1;
}
"-v" | "--verbose" => {
verbose_count += 1;
}
"--output" | "-o" => {
i += 1;
if i < args.len() {
output_files.push(args[i].clone());
output_count += 1;
} else {
eprintln!("Error: Missing output file after --output");
std::process::exit(1);
}
}
arg => {
println!("Argument: {}", arg);
}
}
i += 1;
}
debug!("_precount: {}", _pre_count);
debug!("_midcount: {}", _mid_count);
debug!("_postcount: {}", _post_count);
debug!("verbose_count: {}", verbose_count);
debug!(
"output_files: {:?} output_count: {} ",
output_files, output_count
);
if _pre_count > 0 {
let _do_it_handle = pre().await; //print before ratatui
// Setup terminal
}
let backend = ratatui::backend::CrosstermBackend::new(io::stdout());
let mut terminal = Terminal::new(backend)?;
terminal.hide_cursor()?;
crossterm::execute!(
terminal.backend_mut(),
crossterm::terminal::EnterAlternateScreen,
crossterm::cursor::MoveTo(0, 0)
)?;
terminal.clear()?;
if _mid_count > 0 {
let _do_it_handle = mid().await;
}
// Your ratatui drawing code here (e.g., terminal.draw(...))
terminal.draw(|f| {
use ratatui::{/*prelude::*,*/ widgets::*};
let block = Block::default()
.title(" crossterm lifecycle (alternate screen) ")
.borders(Borders::ALL);
f.render_widget(block, f.size());
})?;
//let do_it_handle = do_it().await;//prints below ratatui screen
// Wait for a key press before exiting
if crossterm::event::poll(std::time::Duration::from_millis(5000))? {
crossterm::event::read()?;
}
// Restore terminal settings and exit alternate screen
crossterm::execute!(
terminal.backend_mut(),
crossterm::terminal::LeaveAlternateScreen,
crossterm::cursor::Show
)?;
if _post_count > 0 {
let _do_it_handle = post().await; //prints below ratatui screen after exit
}
Ok(())
}
async fn mid() -> Result<(), Box<dyn std::error::Error>> {
println!("");
let mut child = Command::new("ls")
.arg("-l")
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()?;
let stdout = child.stdout.take().expect("stdout was not configured");
let stderr = child.stderr.take().expect("stderr was not configured");
let stdout_reader = BufReader::new(stdout);
let stderr_reader = BufReader::new(stderr);
let stdout_handle = spawn(async move {
let mut lines = stdout_reader.lines();
while let Some(line) = lines.next_line().await.unwrap_or(Some("0".to_string())) {
//spacing for ratatui border
println!(" {}", line);
}
});
let stderr_handle = spawn(async move {
let mut lines = stderr_reader.lines();
while let Some(line) = lines.next_line().await.unwrap_or(Some("0".to_string())) {
eprintln!("stderr: {}", line);
}
});
let _status = child.wait().await?;
stdout_handle.await?;
stderr_handle.await?;
//println!("Exited with status: {}", _status);
let option_handle = spawn(async move {
process_options(Some(10), Some("mid-hello".to_string())).await;
});
option_handle.await?;
let option_handle = spawn(async move {
process_options(Some(20), None).await;
});
option_handle.await?;
let option_handle = spawn(async move {
process_options(None, Some("mid-world".to_string())).await;
});
option_handle.await?;
Ok(())
}
async fn post() -> Result<(), Box<dyn std::error::Error>> {
let mut child = Command::new("echo")
.arg("post")
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()?;
let stdout = child.stdout.take().expect("stdout was not configured");
let stderr = child.stderr.take().expect("stderr was not configured");
let stdout_reader = BufReader::new(stdout);
let stderr_reader = BufReader::new(stderr);
let stdout_handle = spawn(async move {
let mut lines = stdout_reader.lines();
while let Some(line) = lines.next_line().await.unwrap_or(Some("0".to_string())) {
println!("{}", line);
}
});
let stderr_handle = spawn(async move {
let mut lines = stderr_reader.lines();
while let Some(line) = lines.next_line().await.unwrap_or(Some("0".to_string())) {
eprintln!("stderr: {}", line);
}
});
let _status = child.wait().await?;
stdout_handle.await?;
stderr_handle.await?;
//println!("Exited with status: {}", _status);
let option_handle = spawn(async move {
process_options(Some(10), Some("post-hello".to_string())).await;
});
option_handle.await?;
let option_handle = spawn(async move {
process_options(Some(20), None).await;
});
option_handle.await?;
let option_handle = spawn(async move {
process_options(None, Some("post-world".to_string())).await;
});
option_handle.await?;
Ok(())
}
#[allow(unused_assignments)]
async fn process_options(mut opt1: Option<i32>, mut opt2: Option<String>) {
let mut o1 = opt1.clone();
let mut o2 = opt2.clone();
while o1.is_some() || o2.is_some() {
match (o1.take(), o2.take()) {
(Some(val1), Some(val2)) => {
//prints twice - once for each option
println!(" Both Some: {}, {}", val1, val2);
// process both values
}
(Some(val1), None) => {
println!(" Opt1 Some: {}", val1);
// process opt1
}
(None, Some(val2)) => {
println!(" Opt2 Some: {}", val2);
// process opt2
}
(None, None) => {
//should not happen since we already checked is_some() above.
}
}
// o1 = opt1;
// o2 = opt2;
opt1 = None;
opt2 = None;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment