use crate::error::CliError;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum InputSource {
File(PathBuf),
Stdin,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum OutputTarget {
File(PathBuf),
Stdout,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Geometry {
pub(crate) width: u32,
pub(crate) height: u32,
}
impl Default for Geometry {
fn default() -> Self {
Self {
width: 1920,
height: 1080,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum CliMode {
FromInput {
input: InputSource,
},
Synthetic {
count: usize,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CliArgs {
pub(crate) mode: CliMode,
pub(crate) geometry: Geometry,
pub(crate) output: OutputTarget,
}
pub(crate) fn parse_args(args: &[String]) -> Result<CliArgs, CliError> {
let mut input: Option<InputSource> = None;
let mut synthetic_count: Option<usize> = None;
let mut geometry = Geometry::default();
let mut output: Option<OutputTarget> = None;
let mut i = 0;
while i < args.len() {
let arg = args[i].as_str();
match arg {
"-i" => {
i += 1;
let value = args
.get(i)
.ok_or_else(|| CliError::Usage("-i requires a file path (or -)".to_owned()))?;
input = Some(if value == "-" {
InputSource::Stdin
} else {
InputSource::File(PathBuf::from(value))
});
}
"-s" => {
i += 1;
let value = args.get(i).ok_or_else(|| {
CliError::Usage("-s requires WxH (e.g. 1920x1080)".to_owned())
})?;
geometry = parse_geometry(value)?;
}
"-y" => {}
"--synthetic" => {
i += 1;
let value = args.get(i).ok_or_else(|| {
CliError::Usage("--synthetic requires a packet count".to_owned())
})?;
let count: usize = value.parse().map_err(|_err| {
CliError::Usage(format!("--synthetic count is not a number: {value}"))
})?;
synthetic_count = Some(count);
}
flag if flag.len() > 1 && flag.starts_with('-') => {
return Err(CliError::Usage(format!("unsupported option: {flag}")));
}
positional => {
if output.is_some() {
return Err(CliError::Usage(format!(
"unexpected extra argument: {positional}"
)));
}
output = Some(if positional == "-" {
OutputTarget::Stdout
} else {
OutputTarget::File(PathBuf::from(positional))
});
}
}
i += 1;
}
let output = output.ok_or_else(|| {
CliError::Usage("missing output (usage: mediaway-avcli -i <input> <output>)".to_owned())
})?;
let mode = match (input, synthetic_count) {
(Some(_), Some(_)) => {
return Err(CliError::Usage(
"-i and --synthetic are mutually exclusive".to_owned(),
));
}
(Some(input), None) => CliMode::FromInput { input },
(None, Some(count)) => CliMode::Synthetic { count },
(None, None) => {
return Err(CliError::Usage(
"missing input: pass -i <path> (or -) or --synthetic <n>".to_owned(),
));
}
};
Ok(CliArgs {
mode,
geometry,
output,
})
}
fn parse_geometry(value: &str) -> Result<Geometry, CliError> {
let (w, h) = value
.split_once('x')
.ok_or_else(|| CliError::Usage(format!("invalid -s geometry (want WxH): {value}")))?;
let width: u32 = w
.parse()
.map_err(|_err| CliError::Usage(format!("invalid -s width: {w}")))?;
let height: u32 = h
.parse()
.map_err(|_err| CliError::Usage(format!("invalid -s height: {h}")))?;
if width == 0 || height == 0 {
return Err(CliError::Usage(format!(
"invalid -s geometry (want WxH > 0): {value}"
)));
}
Ok(Geometry { width, height })
}
#[cfg(test)]
#[path = "args_tests.rs"]
mod tests;