use std::{io, path::PathBuf};
use clap::{value_parser, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint};
use clap_complete::{Generator, Shell};
use image::{error::ImageFormatHint, ImageError, ImageFormat};
use crate::color::Color;
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Parser)]
#[command(
version,
about,
max_term_width(100),
propagate_version(true),
arg_required_else_help(true),
args_conflicts_with_subcommands(true)
)]
pub struct Opt {
#[arg(long, value_enum, value_name("SHELL"))]
pub generate_completion: Option<Shell>,
#[command(subcommand)]
pub command: Option<Command>,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Encode(Encode),
Decode(Decode),
}
#[derive(Args, Debug)]
pub struct Encode {
#[arg(short, long, value_name("FILE"))]
pub output: Option<PathBuf>,
#[arg(
short,
long,
value_name("FILE"),
value_hint(ValueHint::FilePath),
conflicts_with("input")
)]
pub read_from: Option<PathBuf>,
#[arg(
short('l'),
long,
value_enum,
default_value_t,
visible_alias("level"),
value_name("LEVEL"),
ignore_case(true)
)]
pub error_correction_level: Ecc,
#[arg(
value_parser(value_parser!(i16).range(1..=40)),
short('v'),
long,
visible_alias("symversion"),
value_name("NUMBER")
)]
pub symbol_version: Option<i16>,
#[arg(short, long, default_value("4"), value_name("NUMBER"))]
pub margin: u32,
#[arg(
short('t'),
long("type"),
value_enum,
default_value_t,
value_name("FORMAT"),
ignore_case(true)
)]
pub output_format: OutputFormat,
#[arg(
long,
value_enum,
default_value_t,
value_name("MODE"),
ignore_case(true)
)]
pub mode: Mode,
#[arg(
long,
value_enum,
default_value_t,
requires("symbol_version"),
value_name("TYPE"),
ignore_case(true)
)]
pub variant: Variant,
#[arg(long, default_value("#000000"), value_name("COLOR"))]
pub foreground: Color,
#[arg(long, default_value("#ffffff"), value_name("COLOR"))]
pub background: Color,
#[arg(long)]
pub verbose: bool,
#[arg(value_name("STRING"))]
pub input: Option<String>,
}
#[derive(Args, Debug)]
pub struct Decode {
#[arg(
short('t'),
long("type"),
value_enum,
value_name("FORMAT"),
ignore_case(true)
)]
pub input_format: Option<InputFormat>,
#[arg(long, conflicts_with("metadata"))]
pub verbose: bool,
#[arg(long)]
pub metadata: bool,
#[arg(value_name("IMAGE"), value_hint(ValueHint::FilePath))]
pub input: Option<PathBuf>,
}
impl Opt {
pub fn print_completion(gen: impl Generator) {
clap_complete::generate(
gen,
&mut Self::command(),
Self::command().get_name(),
&mut io::stdout(),
);
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, ValueEnum)]
pub enum Ecc {
L,
#[default]
M,
Q,
H,
}
impl From<Ecc> for qrcode::EcLevel {
fn from(level: Ecc) -> Self {
match level {
Ecc::L => Self::L,
Ecc::M => Self::M,
Ecc::Q => Self::Q,
Ecc::H => Self::H,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, ValueEnum)]
pub enum OutputFormat {
#[default]
Png,
Svg,
Terminal,
}
impl TryFrom<OutputFormat> for ImageFormat {
type Error = ImageError;
fn try_from(format: OutputFormat) -> Result<Self, Self::Error> {
match format {
OutputFormat::Png => Ok(Self::Png),
_ => Err(Self::Error::Unsupported(ImageFormatHint::Unknown.into())),
}
}
}
#[derive(Clone, Debug, Default, ValueEnum)]
pub enum Mode {
Numeric,
Alphanumeric,
#[default]
Byte,
Kanji,
}
#[derive(Clone, Debug, Default, ValueEnum)]
pub enum Variant {
#[default]
Normal,
Micro,
}
#[derive(Clone, Debug, ValueEnum)]
#[value(rename_all = "lower")]
pub enum InputFormat {
Bmp,
Dds,
Farbfeld,
Gif,
Hdr,
Ico,
Jpeg,
Png,
Pnm,
#[cfg(feature = "decode-from-svg")]
Svg,
Tga,
Tiff,
WebP,
}
impl TryFrom<InputFormat> for ImageFormat {
type Error = ImageError;
fn try_from(format: InputFormat) -> Result<Self, Self::Error> {
match format {
InputFormat::Bmp => Ok(Self::Bmp),
InputFormat::Dds => Ok(Self::Dds),
InputFormat::Farbfeld => Ok(Self::Farbfeld),
InputFormat::Gif => Ok(Self::Gif),
InputFormat::Hdr => Ok(Self::Hdr),
InputFormat::Ico => Ok(Self::Ico),
InputFormat::Jpeg => Ok(Self::Jpeg),
InputFormat::Png => Ok(Self::Png),
InputFormat::Pnm => Ok(Self::Pnm),
#[cfg(feature = "decode-from-svg")]
InputFormat::Svg => Err(Self::Error::Unsupported(ImageFormatHint::Unknown.into())),
InputFormat::Tga => Ok(Self::Tga),
InputFormat::Tiff => Ok(Self::Tiff),
InputFormat::WebP => Ok(Self::WebP),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verify_app() {
Opt::command().debug_assert();
}
}