From 464f52f445d5a8295ca253b7e70cf7272173a674 Mon Sep 17 00:00:00 2001 From: tippfehlr Date: Mon, 20 May 2024 00:27:25 +0200 Subject: [PATCH] ravedude: add newline_after and newline_on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit newline_after adds a newline after the n’th byte newline_on adds a newline after the given byte --- ravedude/src/console.rs | 50 ++++++++++++++++----------- ravedude/src/main.rs | 76 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 99 insertions(+), 27 deletions(-) diff --git a/ravedude/src/console.rs b/ravedude/src/console.rs index 8ee810c0f0..8944fd41ef 100644 --- a/ravedude/src/console.rs +++ b/ravedude/src/console.rs @@ -1,4 +1,3 @@ -use anyhow::Context as _; use std::io::Read as _; use std::io::Write as _; @@ -32,6 +31,7 @@ pub fn open(args: Args) -> anyhow::Result<()> { task_message!("", "{}", "CTRL+C to exit.".dimmed()); // Empty line for visual consistency eprintln!(); + let mut rx = serialport::new(port.to_string_lossy(), baudrate) .timeout(std::time::Duration::from_secs(2)) .open_native() @@ -43,13 +43,29 @@ pub fn open(args: Args) -> anyhow::Result<()> { // Set a CTRL+C handler to terminate cleanly instead of with an error. ctrlc::set_handler(move || { - eprintln!(""); + eprintln!(); eprintln!("Exiting."); std::process::exit(0); }) .context("failed setting a CTRL+C handler")?; + let newline_after = match args.newline_after { + Some(n) => n, + None => match args.output_mode { + Some(Hex) | Some(Dec) => 16, + Some(Bin) => 8, + _ => 0, + }, + }; + + let (spaces, space_after) = if args.newline_on.is_none() && newline_after % 4 == 0 { + (true, 4) + } else { + (false, 0) + }; + let mut byte_count = 0; + // Spawn a thread for the receiving end because stdio is not portably non-blocking... std::thread::spawn(move || loop { #[cfg(not(target_os = "windows"))] @@ -82,32 +98,26 @@ pub fn open(args: Args) -> anyhow::Result<()> { Some(Ascii) | None => unreachable!(), Some(Hex) => { write!(stdout, "{:02x} ", byte).unwrap(); - if byte_count % 4 == 0 { - write!(stdout, " ").unwrap(); - } - if byte_count % 16 == 0 { - writeln!(stdout).unwrap(); - } } Some(Dec) => { write!(stdout, "{:03} ", byte).unwrap(); - if byte_count % 4 == 0 { - write!(stdout, " ").unwrap(); - } - if byte_count % 16 == 0 { - writeln!(stdout).unwrap(); - } } Some(Bin) => { write!(stdout, "{:08b} ", byte).unwrap(); - if byte_count % 4 == 0 { - write!(stdout, " ").unwrap(); - } - if byte_count % 8 == 0 { - writeln!(stdout).unwrap(); - } } } + // don’t execute in ascii mode, ascii is unreachable here + if spaces && byte_count % space_after == 0 { + write!(stdout, " ").unwrap(); + } + if args.newline_on.is_none() && byte_count % newline_after == 0 { + writeln!(stdout).unwrap(); + } + if args.newline_on.is_some() + && *byte as char == args.newline_on.unwrap() + { + writeln!(stdout).unwrap(); + } } } } diff --git a/ravedude/src/main.rs b/ravedude/src/main.rs index 25e7cfc308..3408594cf4 100644 --- a/ravedude/src/main.rs +++ b/ravedude/src/main.rs @@ -1,4 +1,4 @@ -use anyhow::Context as _; +use anyhow::{bail, Context as _}; use colored::Colorize as _; use structopt::clap::AppSettings; @@ -35,6 +35,51 @@ impl FromStr for OutputMode { } } +// this could have fewer nested if’s, but this produces better error messages +fn parse_newline_on(s: &str) -> Result { + if let Ok(c) = s.parse::() { + Ok(c) + + // if it starts with 0x then parse the hex byte + } else if &s[0..2] == "0x" { + if s.len() == 4 { + if let Ok(n) = u8::from_str_radix(&s[2..4], 16) { + Ok(n as char) + } else { + bail!("invalid hex byte") + } + } else { + bail!("hex byte must have 2 characters") + } + // if it starts with 0b then parse the binary byte + } else if &s[0..2] == "0b" { + if s.len() == 10 { + if let Ok(n) = u8::from_str_radix(&s[2..10], 2) { + Ok(n as char) + } else { + bail!("invalid binary byte") + } + } else { + bail!("binary byte must have 8 characters") + } + } else { + bail!("must be a single character or a byte in hex or binary notation") + } +} + +#[test] +fn test_parse_newline_on() { + assert_eq!(parse_newline_on("a").unwrap(), 'a'); + assert_eq!(parse_newline_on("\n").unwrap(), '\n'); + assert_eq!(parse_newline_on("0x41").unwrap(), 'A'); + assert_eq!(parse_newline_on("0b01000001").unwrap(), 'A'); + assert!(parse_newline_on("not a char").is_err()); + assert!(parse_newline_on("0x").is_err()); + assert!(parse_newline_on("0xzz").is_err()); + assert!(parse_newline_on("0b").is_err()); + assert!(parse_newline_on("0b0a0a0a0a").is_err()); +} + /// ravedude is a rust wrapper around avrdude for providing the smoothest possible development /// experience with rust on AVR microcontrollers. /// @@ -74,6 +119,25 @@ struct Args { #[structopt(long = "debug-avrdude")] debug_avrdude: bool, + /// Output mode. + /// + /// Can be ascii, hex, dec or bin + #[structopt(short = "o")] + output_mode: Option, + + /// Print a newline after this byte + /// not used with output_mode ascii + /// hex (0x) and bin (0b) notations are supported. + #[structopt(long = "newline-on", parse(try_from_str = parse_newline_on))] + newline_on: Option, + + /// Print a newline after n bytes + /// not used with output_mode ascii + /// defaults to 16 for hex and dec and 8 for bin + /// if dividable by 4, bytes will be grouped to 4 + #[structopt(long = "newline-after")] + newline_after: Option, + /// Which board to interact with. /// /// Must be one of the known board identifiers: @@ -101,12 +165,6 @@ struct Args { /// If no binary is given, flashing will be skipped. #[structopt(name = "BINARY", parse(from_os_str))] bin: Option, - - /// Output mode. - /// - /// Can be ascii, hex, dec or bin - #[structopt(short = "o")] - output_mode: Option, } fn main() { @@ -125,6 +183,10 @@ fn ravedude() -> anyhow::Result<()> { let board = board::get_board(&args.board).expect("board not found"); + if args.newline_on.is_some() && args.newline_after.is_some() { + bail!("newline_on and newline_after cannot be used at the same time"); + } + task_message!("Board", "{}", board.display_name()); if let Some(wait_time) = args.reset_delay {