Revised parsing of command line arguments.

This commit is contained in:
shy 2021-04-08 07:59:11 +02:00
parent 066b88eeee
commit 1e3e3d9fea
4 changed files with 78 additions and 42 deletions

View file

@ -279,9 +279,9 @@ pub fn alarm_exec(config: &Config, elapsed: u32) {
} }
if let Some(exec) = &config.alarm_exec { if let Some(exec) = &config.alarm_exec {
// Replace every occurrence of "%s". // Replace every occurrence of "{}".
for s in exec { for s in exec {
args.push(s.replace("%s", &time)); args.push(s.replace("{}", &time));
} }
if Command::new(&exec[0]) if Command::new(&exec[0])

View file

@ -101,7 +101,7 @@ impl Clock {
let day_count = format!( let day_count = format!(
"+ {} {}", "+ {} {}",
self.days, self.days,
if self.days == 1 { "day" } else { "days" }); if self.days == 1 { "DAY" } else { "DAYS" });
write!(stdout, write!(stdout,
"{}{:>11}", "{}{:>11}",

View file

@ -108,7 +108,7 @@ impl Layout {
// Days (based on position of seconds). // Days (based on position of seconds).
self.clock_days = Position { self.clock_days = Position {
line: self.clock_sec.line + DIGIT_HEIGHT + 1, line: self.clock_sec.line + DIGIT_HEIGHT,
col: self.clock_sec.col, col: self.clock_sec.col,
}; };

View file

@ -22,18 +22,19 @@ use layout::{Layout, Position};
const NAME: &str = env!("CARGO_PKG_NAME"); const NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION"); const VERSION: &str = env!("CARGO_PKG_VERSION");
const USAGE: &str = concat!("USAGE: ", env!("CARGO_PKG_NAME"), const USAGE: &str = concat!("USAGE: ", env!("CARGO_PKG_NAME"),
" [-h|-v] [-p] [-q] [ALARM TIME(s)] [-e|--exec COMMAND [...]] " [-h|-v] [-e|--exec COMMAND] [-p] [-q] [ALARM TIME(s)]
PARAMETERS: PARAMETERS:
[ALARM TIME] None or multiple alarm times (HH:MM:SS). [ALARM TIME] None or multiple alarm times (HH:MM:SS).
OPTIONS: OPTIONS:
-h, --help Display this help. -h, --help Show this help and exit.
-v, --version Display version information. -v, --version Show version information and exit.
-e, --exec [COMMAND] Execute COMMAND on alarm. Every occurrence of {}
will be replaced by the elapsed time in (HH:)MM:SS
format.
-p, --plain Use simpler block chars. -p, --plain Use simpler block chars.
-q, --quit Quit program after last alarm. -q, --quit Quit program after last alarm.
-e, --exec [COMMAND] Execute COMMAND on alarm. Must be the last flag on
the command line. Everything after it is passed as
argument to COMMAND. Every \"{}\" will be replaced
by the elapsed time in (HH:)MM:SS format.
SIGNALS: <SIGUSR1> Reset clock. SIGNALS: <SIGUSR1> Reset clock.
<SIGUSR2> Pause or un-pause clock."); <SIGUSR2> Pause or un-pause clock.");
@ -314,42 +315,77 @@ fn usage() {
// Parse command line arguments into "config". // Parse command line arguments into "config".
fn parse_args(config: &mut Config, alarm_roster: &mut AlarmRoster) { fn parse_args(config: &mut Config, alarm_roster: &mut AlarmRoster) {
for arg in env::args().skip(1) { let mut iter = env::args().skip(1);
match arg.as_str() {
"-h" | "--help" => usage(), loop {
"-v" | "--version" => { if let Some(arg) = iter.next() {
println!("{} {}", NAME, VERSION); match arg.as_str() {
std::process::exit(0); "-h" | "--help" => usage(),
}, "-v" | "--version" => {
"-p" | "--plain" => { config.plain = true; }, println!("{} {}", NAME, VERSION);
"-q" | "--quit" => { config.auto_quit = true; }, std::process::exit(0);
"-e" | "--exec" => { },
// Find position of this flag. "-p" | "--plain" => config.plain = true,
let i = env::args().position(|s| { s == "-e" || s == "--exec" }).unwrap(); "-q" | "--quit" => config.auto_quit = true,
// Copy everything thereafter. "-e" | "--exec" => {
let exec: Vec<String> = env::args().skip(i + 1).collect(); if let Some(e) = iter.next() {
if exec.is_empty() { config.alarm_exec = Some(input_to_exec(&e));
usage(); } else {
} else { println!("Missing parameter to \"{}\".", arg);
config.alarm_exec = Some(exec); std::process::exit(1);
// Ignore everything after this flag. }
break; },
} any if any.starts_with('-') => {
}, // Unrecognized flag.
any if any.starts_with('-') => { println!("Unrecognized option: \"{}\"", any);
// Unrecognized flag. println!("Use \"-h\" or \"--help\" for a list of valid command line options");
println!("Unrecognized option: \"{}\"", any);
println!("Use \"-h\" or \"--help\" for a list of valid command line options");
std::process::exit(1);
},
any => {
if let Err(error) = alarm_roster.add(&String::from(any)) {
println!("Error adding \"{}\" as alarm. ({})", any, error);
std::process::exit(1); std::process::exit(1);
},
any => {
// Alarm to add.
if let Err(error) = alarm_roster.add(&String::from(any)) {
println!("Error adding \"{}\" as alarm. ({})", any, error);
std::process::exit(1);
}
},
}
} else { break; } // All command line parameters processed.
}
}
// Parse command line argument to --command into a vector of strings suitable
// for process::Command::spawn.
fn input_to_exec(input: &str) -> Vec<String> {
let mut exec: Vec<String> = Vec::new();
let mut subs: String = String::new();
let mut quoted = false;
let mut escaped = false;
for byte in input.chars() {
match byte {
'\\' if !escaped => {
// Next char is escaped.
escaped = true;
continue;
},
' ' if escaped || quoted => { &subs.push(' '); },
' ' => {
if !&subs.is_empty() {
exec.push(subs.clone());
&subs.clear();
} }
}, },
'"' | '\'' if !escaped => quoted = !quoted,
_ => {
if escaped { &subs.push('\\'); }
&subs.push(byte);
},
} }
escaped = false;
} }
exec.push(subs.clone());
exec
} }
fn register_signal_handlers(signal: &Arc<AtomicUsize>, layout: &Layout) { fn register_signal_handlers(signal: &Arc<AtomicUsize>, layout: &Layout) {