use crate::config::FrameConfig;
use crate::errors::OliframeError;
use crate::geometry::{AspectRatio, Margins, RelativePosition};
use clap::Args;
use csscolorparser::Color;
use image::Rgba;
use std::str::FromStr;
#[derive(Args, Debug)]
pub struct FrameOptions {
#[arg(long = "ar", value_name = "RATIO", help_heading = "Framing Options")]
aspect_ratio: Option<String>,
#[arg(short = 'c', long, value_name = "COLOR", default_value = "white")]
color: String,
#[arg(short = 'r', long = "radius", value_name = "RADIUS")]
corner_radius: Option<u32>,
#[arg(short = 'm', long, value_name = "SIZE(S)")]
margins: Option<String>,
#[arg(short = 'P', long, value_name = "POSITION")]
position: Option<String>,
}
impl TryFrom<FrameOptions> for FrameConfig {
type Error = OliframeError;
fn try_from(opts: FrameOptions) -> Result<Self, Self::Error> {
let aspect_ratio = opts
.aspect_ratio
.map(|ar| AspectRatio::from_str(&ar))
.transpose()?;
let color = opts
.color
.parse::<Color>()
.map(|c| Rgba(c.to_rgba8()))
.map_err(|_| OliframeError::InvalidInput("Invalid color.".to_string()))?;
let margins = opts
.margins
.map(|m| Margins::from_str(&m))
.transpose()?
.unwrap_or_default();
let position = opts
.position
.map(|p| RelativePosition::from_str(&p))
.transpose()?
.unwrap_or_default();
Ok(FrameConfig::new(
aspect_ratio,
color,
opts.corner_radius,
margins,
position,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_frame_options() {
let opts = FrameOptions {
aspect_ratio: Some("16:9".to_string()),
color: "black".to_string(),
corner_radius: Some(5),
margins: Some("10".to_string()),
position: Some("center".to_string()),
};
let config = FrameConfig::try_from(opts).unwrap();
assert_eq!(config.aspect_ratio().as_ref().unwrap().inner(), 16.0 / 9.0);
assert_eq!(config.color(), &Rgba([0, 0, 0, 255]));
assert_eq!(config.corner_radius(), &Some(5));
assert_eq!(config.margins().values(), &[0.1]);
assert_eq!(config.position(), &RelativePosition::default());
}
#[test]
fn parse_frame_options_with_defaults() {
let opts = FrameOptions {
aspect_ratio: None,
color: "white".to_string(),
corner_radius: None,
margins: None,
position: None,
};
let config = FrameConfig::try_from(opts).unwrap();
assert!(config.aspect_ratio().is_none());
assert_eq!(config.color(), &Rgba([255, 255, 255, 255]));
assert!(config.corner_radius().is_none());
assert_eq!(config.margins().values(), &[0.05]);
assert_eq!(config.position(), &RelativePosition::default());
}
#[test]
fn parsing_with_invalid_color_fails() {
let opts = FrameOptions {
aspect_ratio: None,
color: "invalid".to_string(),
corner_radius: None,
margins: None,
position: None,
};
let result = FrameConfig::try_from(opts);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Invalid input: Invalid color."
);
}
}