Skip to content

Commit c8619ed

Browse files
committed
Add commandline argument parser
It is often useful to be able to directly specify a serial port and its settings from the commandline rather than having to select it in the GUI each time. Add a CLI parser based on `gumdrop` to parse commandline arguments. The selected serial port is then immediately opened on startup.
1 parent 6727350 commit c8619ed

File tree

3 files changed

+114
-7
lines changed

3 files changed

+114
-7
lines changed

Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ tempfile = { version = "3.15", optional = true }
2626
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls", "http2"], optional = true }
2727
semver = { version = "1.0.24", optional = true }
2828
crossbeam-channel = "0.5.14"
29+
gumdrop = "0.8.1"
2930

3031
[target.'cfg(not(target_os = "ios"))'.dependencies]
3132
eframe = { version = "0.32", features = ["persistence", "wayland", "x11"] }

src/main.rs

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ use crossbeam_channel::{select, Receiver, Sender};
1313
use eframe::egui::{vec2, ViewportBuilder, Visuals};
1414
use eframe::{egui, icon_data};
1515
use egui_plot::PlotPoint;
16+
pub use gumdrop::Options;
1617
use preferences::AppInfo;
1718
use std::cmp::max;
1819
use std::path::PathBuf;
1920
use std::sync::{Arc, RwLock};
21+
use std::thread;
2022
use std::time::Duration;
21-
use std::{env, thread};
2223

2324
mod color_picker;
2425
mod custom_highlighter;
@@ -262,13 +263,100 @@ fn main_thread(
262263
}
263264
}
264265

266+
fn parse_databits(s: &str) -> Result<serialport::DataBits, String> {
267+
let d: u8 = s
268+
.parse()
269+
.map_err(|_e| format!("databits not a number: {s}"))?;
270+
Ok(serialport::DataBits::try_from(d).map_err(|_e| format!("invalid databits: {s}"))?)
271+
}
272+
273+
fn parse_flow(s: &str) -> Result<serialport::FlowControl, String> {
274+
match s {
275+
"none" => Ok(serialport::FlowControl::None),
276+
"soft" => Ok(serialport::FlowControl::Software),
277+
"hard" => Ok(serialport::FlowControl::Hardware),
278+
_ => Err(format!("invalid flow-control: {s}")),
279+
}
280+
}
281+
282+
fn parse_stopbits(s: &str) -> Result<serialport::StopBits, String> {
283+
let d: u8 = s
284+
.parse()
285+
.map_err(|_e| format!("stopbits not a number: {s}"))?;
286+
Ok(serialport::StopBits::try_from(d).map_err(|_e| format!("invalid stopbits: {s}"))?)
287+
}
288+
289+
fn parse_parity(s: &str) -> Result<serialport::Parity, String> {
290+
match s {
291+
"none" => Ok(serialport::Parity::None),
292+
"odd" => Ok(serialport::Parity::Odd),
293+
"even" => Ok(serialport::Parity::Even),
294+
_ => Err(format!("invalid parity setting: {s}")),
295+
}
296+
}
297+
298+
#[derive(Debug, Options)]
299+
struct CliOptions {
300+
/// Serial port device to open on startup
301+
#[options(free)]
302+
device: Option<String>,
303+
304+
/// Baudrate (default=9600)
305+
#[options(short = "b")]
306+
baudrate: Option<u32>,
307+
308+
/// Data bits (5, 6, 7, default=8)
309+
#[options(short = "d", parse(try_from_str = "parse_databits"))]
310+
databits: Option<serialport::DataBits>,
311+
312+
/// Flow conrol (hard, soft, default=none)
313+
#[options(short = "f", parse(try_from_str = "parse_flow"))]
314+
flow: Option<serialport::FlowControl>,
315+
316+
/// Stop bits (default=1, 2)
317+
#[options(short = "s", parse(try_from_str = "parse_stopbits"))]
318+
stopbits: Option<serialport::StopBits>,
319+
320+
/// Parity (odd, even, default=none)
321+
#[options(short = "p", parse(try_from_str = "parse_parity"))]
322+
parity: Option<serialport::Parity>,
323+
324+
/// Load data from a file instead of a serial port
325+
#[options(short = "F")]
326+
file: Option<std::path::PathBuf>,
327+
328+
help: bool,
329+
}
330+
265331
fn main() {
266332
egui_logger::builder().init().unwrap();
267333

334+
let args = CliOptions::parse_args_default_or_exit();
335+
268336
let gui_settings = load_gui_settings();
269337
let saved_serial_device_configs = load_serial_settings();
270338

271-
let device_lock = Arc::new(RwLock::new(Device::default()));
339+
let mut device = Device::default();
340+
if let Some(name) = args.device {
341+
device.name = name;
342+
}
343+
if let Some(baudrate) = args.baudrate {
344+
device.baud_rate = baudrate;
345+
}
346+
if let Some(databits) = args.databits {
347+
device.data_bits = databits;
348+
}
349+
if let Some(flow) = args.flow {
350+
device.flow_control = flow;
351+
}
352+
if let Some(stopbits) = args.stopbits {
353+
device.stop_bits = stopbits;
354+
}
355+
if let Some(parity) = args.parity {
356+
device.parity = parity;
357+
}
358+
359+
let device_lock = Arc::new(RwLock::new(device));
272360
let devices_lock = Arc::new(RwLock::new(vec![gui_settings.device.clone()]));
273361
let data_lock = Arc::new(RwLock::new(GuiOutputDataContainer::default()));
274362
let connected_lock = Arc::new(RwLock::new(false));
@@ -319,11 +407,8 @@ fn main() {
319407
);
320408
});
321409

322-
let args: Vec<String> = env::args().collect();
323-
if args.len() > 1 {
324-
load_tx
325-
.send(PathBuf::from(&args[1]))
326-
.expect("failed to send file");
410+
if let Some(file) = args.file {
411+
load_tx.send(file).expect("failed to send file");
327412
}
328413

329414
let options = eframe::NativeOptions {

0 commit comments

Comments
 (0)