1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
use lexopt::ValueExt as _;
use rustix::fd::FromRawFd as _;
use std::fs::File;
pub(crate) enum Command {
Check,
Daemon { ready_fd: Option<File> },
Sync { dry_run: bool },
ResolveConflicts { dry_run: bool },
Discover,
Version,
}
pub(crate) struct Cli {
pub command: Command,
pub log_level: log::LevelFilter,
pub config_file: Option<String>,
pub pairs: Option<Vec<String>>,
}
impl Cli {
pub fn parse(mut args: impl Iterator<Item = String>) -> Result<Cli, lexopt::Error> {
let mut command = None;
let mut log_level = log::LevelFilter::Warn;
let mut config_file: Option<String> = None;
let mut pairs = Vec::new();
args.next(); // Skip arg0
let mut parser = lexopt::Parser::from_args(args);
while let Some(arg) = parser.next()? {
match arg {
lexopt::Arg::Short('v') => log_level = parser.value()?.parse()?,
lexopt::Arg::Short('c') => config_file = Some(parser.value()?.string()?),
lexopt::Arg::Short('p') => {
let pair_name = parser.value()?.string()?;
pairs.push(pair_name);
}
lexopt::Arg::Value(raw_cmd) => {
command = match raw_cmd.string()?.as_str() {
"check" => Some(Command::Check),
"daemon" => {
let mut ready_fd = None;
while let Some(arg) = parser.next()? {
match arg {
lexopt::Arg::Short('r') => {
let raw_fd = parser.value()?.parse()?;
if raw_fd < 3 {
return Err(
"Readiness fd must be greater than 2".into()
);
}
// SAFETY: this file descriptor is not accessed elsewhere.
// The user is responsible for ensuring that they have
// supplied a valid open file.
ready_fd = Some(unsafe { File::from_raw_fd(raw_fd) });
}
_ => return Err(arg.unexpected()),
};
}
Some(Command::Daemon { ready_fd })
}
"sync" => {
let mut dry_run = false;
while let Some(arg) = parser.next()? {
match arg {
lexopt::Arg::Short('d') => dry_run = true,
_ => return Err(arg.unexpected()),
};
}
Some(Command::Sync { dry_run })
}
"resolve-conflicts" => {
let mut dry_run = false;
while let Some(arg) = parser.next()? {
match arg {
lexopt::Arg::Short('d') => dry_run = true,
_ => return Err(arg.unexpected()),
};
}
Some(Command::ResolveConflicts { dry_run })
}
"discover" => Some(Command::Discover),
"version" => Some(Command::Version),
cmd => return Err(format!("Unknown command: {cmd}").into()),
};
break;
}
_ => return Err(arg.unexpected()),
};
}
Ok(Cli {
command: command.ok_or(lexopt::Error::from("No command specified"))?,
log_level,
config_file,
pairs: if pairs.is_empty() { None } else { Some(pairs) },
})
}
}