From c0e3478ae02081ab242124ff2f9a56a80416ad3c Mon Sep 17 00:00:00 2001 From: Breval Ferrari Date: Sat, 31 May 2025 11:36:18 +0200 Subject: [PATCH] split cli, cli utilities --- Cargo.toml | 1 + src/cli/cli.rs | 445 ++++++++++++++++++++++++++++++++++++++++++++++++ src/cli/main.rs | 420 +-------------------------------------------- 3 files changed, 453 insertions(+), 413 deletions(-) create mode 100644 src/cli/cli.rs diff --git a/Cargo.toml b/Cargo.toml index d2aa51f..40bcf44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ derive_builder = "0.20" derive_wrapper = "0.1" fasteval = "0.2" flacenc = { version = "0.4", optional = true } +getset = "0.1.5" hound = { version = "3.5", optional = true } lazy_static = "1.5" mp3lame-encoder = { version = "0.2", features = ["std"], optional = true } diff --git a/src/cli/cli.rs b/src/cli/cli.rs new file mode 100644 index 0000000..eaeb75f --- /dev/null +++ b/src/cli/cli.rs @@ -0,0 +1,445 @@ +#![allow(dead_code)] + +use std::{ + error::Error, + fmt::Debug, + fs::File, + io::{self, Cursor, Read, stdin}, + ops::Not, + str::{Bytes, FromStr}, +}; + +use anyhow::anyhow; +use bliplib::compiler::Expression; +use clap::Parser; +use derive_wrapper::AsRef; +use getset::Getters; +use hound::SampleFormat; +use mp3lame_encoder::{Bitrate, Quality}; +use nom::{ + Finish, Parser as P, + branch::alt, + bytes::complete::tag, + character::complete::{char, u16, usize}, + combinator::{rest, success, value}, + error::ErrorKind, + sequence::preceded, +}; +use nom_locate::{LocatedSpan, position}; +use strum::{EnumDiscriminants, EnumString, IntoDiscriminant, IntoStaticStr}; +use thiserror::Error; + +const DEFAULT_INSTRUMENT: &str = "sin(2*pi()*(442+442*((n+1)/N))*t)"; +const DEFAULT_LENGTH: &str = "2^(2-log(2, l))*(60/T)"; + +#[derive(Parser)] +#[cfg_attr(debug_assertions, derive(Debug))] +#[command(version, author, about)] +pub(super) enum Cli { + /// Play a song + Play(PlayOpts), + /// Export a song to an audio file or stdout + Export(ExportOpts), + /// Memo menu for examples and general help about syntax and supported audio formats + #[command(subcommand)] + Memo(Memo), +} + +#[derive(Parser, Clone, Getters)] +#[cfg_attr(debug_assertions, derive(Debug))] +#[getset(get)] +pub(super) struct PlayOpts { + /// Use this sheet music [default: stdin] + #[command(flatten)] + input: InputGroup, + /// Set available notes ("a,b,c" for example) + #[arg(short, long)] + notes: Vec, + /// Set the signal expression (instrument) used to generate music samples + #[arg(short, long, default_value = DEFAULT_INSTRUMENT)] + instrument: Expression, + /// Set the expression used to generate note lengths in seconds + #[arg(short, long, default_value = DEFAULT_LENGTH)] + length: Expression, + /// Add a variable named VARIABLE (a single letter) and set its initial value to VALUE + #[arg(short, long = "variable", value_name = "VARIABLE=VALUE", value_parser = parse_key_val::<'=', Letter, f64>)] + #[getset(skip)] + variables: Vec<(Letter, f64)>, + /// Add a macro named NAME (single character, not alphanumeric) which expands to EXPANSION when called in sheet music + #[arg(short, long = "macro", value_name = "NAME:EXPANSION", value_parser = parse_key_val::<':', NotALetter, String>)] + #[getset(skip)] + macros: Vec<(NotALetter, String)>, + /// Add a slope expression named NAME which mutates the VARIABLE with the result of EXPR each frame + #[arg(short, long = "slope", value_name = "NAME:VARIABLE=EXPR", value_parser = parse_key_tuple::)] + #[getset(skip)] + slopes: Vec<(String, (LetterString, Expression))>, +} + +impl PlayOpts { + pub(super) fn variables(&self) -> impl Iterator { + self.variables.iter().map(|(c, f)| (c.as_ref(), f)) + } + + pub(super) fn macros(&self) -> impl Iterator { + self.macros.iter().map(|(c, s)| (c.as_ref(), s)) + } + + pub(super) fn slopes(&self) -> impl Iterator { + self.slopes.iter().map(|(id, (s, e))| (id, (s.as_ref(), e))) + } +} + +impl InputGroup { + pub(super) fn get<'a>(&'a self) -> Box { + self.input + .as_ref() + .map(|i| Box::new(i.clone().0) as Box) + .or(self + .sheet_music_string + .as_ref() + .map(|s| Box::new(Cursor::new(s.clone())) as Box)) + .unwrap_or(Box::new(stdin())) + } +} + +#[derive(Clone, Copy, AsRef)] +#[cfg_attr(debug_assertions, derive(Debug))] +struct Letter(char); + +#[derive(Debug, Error)] +enum LetterError { + #[error("the character '{0}' should be a letter")] + Char(char), + #[error("no characters found")] + Empty, +} + +impl FromStr for Letter { + type Err = LetterError; + fn from_str(s: &str) -> Result { + let c = s.chars().next().ok_or(Self::Err::Empty)?; + c.is_alphabetic() + .then_some(c) + .map(Self) + .ok_or(Self::Err::Char(c)) + } +} + +#[derive(Clone, Copy, AsRef)] +#[cfg_attr(debug_assertions, derive(Debug))] +struct NotALetter(char); + +#[derive(Debug, Error)] +enum NotALetterError { + #[error("the character '{0}' should not be a letter")] + Char(char), + #[error("no characters found")] + Empty, +} + +impl FromStr for NotALetter { + type Err = NotALetterError; + fn from_str(s: &str) -> Result { + let c = s.chars().next().ok_or(Self::Err::Empty)?; + c.is_alphabetic() + .not() + .then_some(c) + .map(Self) + .ok_or(Self::Err::Char(c)) + } +} + +#[derive(Clone, AsRef)] +#[cfg_attr(debug_assertions, derive(Debug))] +struct LetterString(String); + +#[derive(Debug, Error)] +enum LetterStringError { + #[error("the string \"{0}\" should be only letters")] + String(String), + #[error("no characters found")] + Empty, +} + +impl FromStr for LetterString { + type Err = LetterStringError; + fn from_str(s: &str) -> Result { + s.is_empty() + .not() + .then_some( + s.chars() + .all(|c| c.is_alphabetic()) + .then_some(s) + .map(str::to_string) + .map(Self) + .ok_or(Self::Err::String(s.to_string())), + ) + .ok_or(Self::Err::Empty)? + } +} + +/// Parse a single key-value pair +/// +/// From https://github.com/clap-rs/clap/blob/6b12a81bafe7b9d013b06981f520ab4c70da5510/examples/typed-derive.rs +fn parse_key_val( + s: &str, +) -> Result<(T, U), Box> +where + T: std::str::FromStr, + T::Err: Error + Send + Sync + 'static, + U: std::str::FromStr, + U::Err: Error + Send + Sync + 'static, +{ + let pos = s + .find(SEP) + .ok_or_else(|| format!("invalid KEY{SEP}value: no `{SEP}` found in `{s}`"))?; + Ok((s[..pos].parse()?, s[pos + 1..].parse()?)) +} + +fn parse_key_tuple( + s: &str, +) -> Result<(T, (U1, U2)), Box> +where + T: std::str::FromStr, + T::Err: Error + Send + Sync + 'static, + U1: std::str::FromStr, + U1::Err: Error + Send + Sync + 'static, + U2: std::str::FromStr, + U2::Err: Error + Send + Sync + 'static, +{ + let pos = s + .find(':') + .ok_or_else(|| format!("invalid KEY:value, no `:` found in `{s}`"))?; + Ok(( + s[..pos].parse()?, + parse_key_val::<'=', _, _>(&s[pos + 1..])?, + )) +} + +#[derive(Parser, Clone)] +#[cfg_attr(debug_assertions, derive(Debug))] +#[group(required = false, multiple = false)] +pub(super) struct InputGroup { + /// Set the path to your sheet music file [default: stdin] + input: Option, + /// Use this sheet music instead of reading from a file or stdin + #[arg(short = 'c')] + sheet_music_string: Option, +} + +#[derive(Debug)] +struct ClonableFile(File); + +impl Clone for ClonableFile { + fn clone(&self) -> Self { + Self(self.0.try_clone().expect("cloning file handle")) + } +} + +impl FromStr for ClonableFile { + type Err = io::Error; + fn from_str(s: &str) -> Result { + File::open(s).map(Self) + } +} + +#[derive(Parser, Clone)] +#[cfg_attr(debug_assertions, derive(Debug))] +pub(super) struct ExportOpts { + #[command(flatten)] + playopts: PlayOpts, + /// Audio format to use + #[cfg_attr( + feature = "mp3", + arg(default_value = "mp3 --bitrate 128 --quality best") + )] + #[cfg_attr( + not(feature = "mp3"), + cfg_attr(feature = "raw", arg(default_value = "raw mulaw")) + )] + #[arg(short, long, value_parser = audio_format_parser)] + format: AudioFormat, + /// Output file [default: stdout] + output: Option, +} + +#[derive(Clone, EnumDiscriminants)] +#[strum_discriminants(derive(EnumString, IntoStaticStr), strum(serialize_all = "lowercase"))] +enum AudioFormat { + Mp3 { + bitrate: Bitrate, + quality: Quality, + }, + Wav { + bps: u16, + sample_format: SampleFormat, + }, + Flac { + bps: usize, + }, + Raw(RawAudioFormat), +} + +#[cfg(debug_assertions)] +impl Debug for AudioFormat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Raw(r) => f.debug_tuple("Raw").field(r).finish(), + _ => self.discriminant().fmt(f), + } + } +} + +impl Default for AudioFormat { + fn default() -> Self { + AudioFormat::Raw(Default::default()) + } +} + +fn audio_format_parser(input: &str) -> Result { + fn mp3<'a>() -> impl P< + LocatedSpan<&'a str>, + Output = AudioFormat, + Error = nom::error::Error>, + > { + preceded( + tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error>>( + AudioFormatDiscriminants::Mp3.into(), + ), + u16.or(success(320)) + .and( + alt(( + value(Quality::Best, tag("b")), + value(Quality::SecondBest, tag("sb")), + value(Quality::NearBest, tag("nb")), + value(Quality::VeryNice, tag("v")), + value(Quality::Nice, tag("n")), + value(Quality::Good, tag("g")), + value(Quality::Decent, tag("d")), + value(Quality::Ok, tag("o")), + value(Quality::SecondWorst, tag("sw")), + value(Quality::Worst, tag("w")), + )) + .or(success(Quality::Good)), + ) + .and(position) + .map_res(|((b, q), p)| { + Ok(AudioFormat::Mp3 { + bitrate: match b { + 8 => Bitrate::Kbps8, + 16 => Bitrate::Kbps16, + 24 => Bitrate::Kbps24, + 32 => Bitrate::Kbps32, + 40 => Bitrate::Kbps40, + 48 => Bitrate::Kbps48, + 64 => Bitrate::Kbps64, + 80 => Bitrate::Kbps80, + 96 => Bitrate::Kbps96, + 112 => Bitrate::Kbps112, + 128 => Bitrate::Kbps128, + 160 => Bitrate::Kbps160, + 192 => Bitrate::Kbps192, + 224 => Bitrate::Kbps224, + 256 => Bitrate::Kbps256, + 320 => Bitrate::Kbps320, + _ => return Err(nom::error::Error::new(p, ErrorKind::Verify)), + }, + quality: q, + }) + }), + ) + } + fn wav<'a>() -> impl P< + LocatedSpan<&'a str>, + Output = AudioFormat, + Error = nom::error::Error>, + > { + preceded( + tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error>>( + AudioFormatDiscriminants::Wav.into(), + ), + u16.or(success(320)) + .and(value(SampleFormat::Float, char('f')).or(success(SampleFormat::Int))) + .map(|(bps, sample_format)| AudioFormat::Wav { bps, sample_format }), + ) + } + fn flac<'a>() -> impl P< + LocatedSpan<&'a str>, + Output = AudioFormat, + Error = nom::error::Error>, + > { + preceded( + tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error>>( + AudioFormatDiscriminants::Flac.into(), + ), + usize + .or(success(320000)) + .map(|bps| AudioFormat::Flac { bps }), + ) + } + fn parser<'a>() -> impl P< + LocatedSpan<&'a str>, + Output = AudioFormat, + Error = nom::error::Error>, + > { + alt(( + mp3::<'a>(), + wav::<'a>(), + flac::<'a>(), + rest.map_res(|r: LocatedSpan<&'a str>| { + Ok::>>(AudioFormat::Raw( + RawAudioFormat::try_from(*r) + .map_err(|_| nom::error::Error::new(r, ErrorKind::Verify))?, + )) + }), + )) + } + parser() + .parse_complete(LocatedSpan::new(input)) + .finish() + .map(|(_, o)| o) + .map_err(|e| anyhow!("{e:?}")) +} + +#[derive(Parser, Clone, EnumString, Default)] +#[cfg_attr(debug_assertions, derive(Debug))] +#[strum(ascii_case_insensitive)] +enum RawAudioFormat { + ALaw, + F32Be, + F32Le, + F64Be, + F64Le, + #[default] + MuLaw, + S8, + S16Be, + S16Le, + S24Be, + S24Le, + S32Be, + S32Le, + U8, + U16Be, + U16Le, + U24Be, + U24Le, + U32Be, + U32Le, +} + +#[derive(Parser, Clone)] +#[cfg_attr(debug_assertions, derive(Debug))] +pub(super) enum Memo { + Syntax, + #[command(subcommand)] + Example(Example), + Formats, +} + +#[derive(Parser, Clone)] +#[cfg_attr(debug_assertions, derive(Debug))] +pub(super) enum Example { + List, + N { id: u8 }, +} diff --git a/src/cli/main.rs b/src/cli/main.rs index 535a06b..e0307e7 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -1,419 +1,13 @@ -#![allow(dead_code)] - -use std::{ - error::Error, - fmt::Debug, - fs::File, - io::{self}, - ops::Not, - str::FromStr, -}; - -use anyhow::anyhow; -use bliplib::compiler::Expression; +mod cli; use clap::Parser; -use derive_wrapper::AsRef; -use hound::SampleFormat; -use mp3lame_encoder::{Bitrate, Quality}; -use nom::{ - Finish, Parser as P, - branch::alt, - bytes::complete::tag, - character::complete::{char, u16, usize}, - combinator::{rest, success, value}, - error::ErrorKind, - sequence::preceded, -}; -use nom_locate::{LocatedSpan, position}; -use strum::{EnumDiscriminants, EnumString, IntoDiscriminant, IntoStaticStr}; -use thiserror::Error; - -const DEFAULT_INSTRUMENT: &str = "sin(2*pi()*(442+442*((n+1)/N))*t)"; -const DEFAULT_LENGTH: &str = "2^(2-log(2, l))*(60/T)"; +use cli::Cli; fn main() { let cli = Cli::parse(); - #[cfg(debug_assertions)] - dbg!(cli); -} - -#[derive(Parser)] -#[cfg_attr(debug_assertions, derive(Debug))] -#[command(version, author, about)] -enum Cli { - /// Play a song - Play(PlayOpts), - /// Export a song to an audio file or stdout - Export(ExportOpts), - /// Memo menu for examples and general help about syntax and supported audio formats - #[command(subcommand)] - Memo(Memo), -} - -#[derive(Parser, Clone)] -#[cfg_attr(debug_assertions, derive(Debug))] -struct PlayOpts { - /// Use this sheet music [default: stdin] - #[command(flatten)] - input: InputGroup, - /// Set available notes ("a,b,c" for example) - #[arg(short, long)] - notes: Vec, - /// Set the signal expression (instrument) used to generate music samples - #[arg(short, long, default_value = DEFAULT_INSTRUMENT)] - instrument: Expression, - /// Set the expression used to generate note lengths in seconds - #[arg(short, long, default_value = DEFAULT_LENGTH)] - length: Expression, - /// Add a variable named VARIABLE (a single letter) and set its initial value to VALUE - #[arg(short, long = "variable", value_name = "VARIABLE=VALUE", value_parser = parse_key_val::<'=', Letter, f64>)] - variables: Vec<(Letter, f64)>, - /// Add a macro named NAME (single character, not alphanumeric) which expands to EXPANSION when called in sheet music - #[arg(short, long = "macro", value_name = "NAME:EXPANSION", value_parser = parse_key_val::<':', NotALetter, String>)] - macros: Vec<(NotALetter, String)>, - /// Add a slope expression named NAME which mutates the VARIABLE with the result of EXPR each frame - #[arg(short, long = "slope", value_name = "NAME:VARIABLE=EXPR", value_parser = parse_key_tuple::)] - slopes: Vec<(String, (LetterString, Expression))>, -} - -#[derive(Clone, Copy, AsRef)] -#[cfg_attr(debug_assertions, derive(Debug))] -struct Letter(char); - -#[derive(Debug, Error)] -enum LetterError { - #[error("the character '{0}' should be a letter")] - Char(char), - #[error("no characters found")] - Empty, -} - -impl FromStr for Letter { - type Err = LetterError; - fn from_str(s: &str) -> Result { - let c = s.chars().next().ok_or(Self::Err::Empty)?; - c.is_alphabetic() - .then_some(c) - .map(Self) - .ok_or(Self::Err::Char(c)) + use Cli::*; + match cli { + Play(play_opts) => todo!(), + Export(export_opts) => todo!(), + Memo(memo) => todo!(), } } - -#[derive(Clone, Copy, AsRef)] -#[cfg_attr(debug_assertions, derive(Debug))] -struct NotALetter(char); - -#[derive(Debug, Error)] -enum NotALetterError { - #[error("the character '{0}' should not be a letter")] - Char(char), - #[error("no characters found")] - Empty, -} - -impl FromStr for NotALetter { - type Err = NotALetterError; - fn from_str(s: &str) -> Result { - let c = s.chars().next().ok_or(Self::Err::Empty)?; - c.is_alphabetic() - .not() - .then_some(c) - .map(Self) - .ok_or(Self::Err::Char(c)) - } -} - -#[derive(Clone, AsRef)] -#[cfg_attr(debug_assertions, derive(Debug))] -struct LetterString(String); - -#[derive(Debug, Error)] -enum LetterStringError { - #[error("the string \"{0}\" should be only letters")] - String(String), - #[error("no characters found")] - Empty, -} - -impl FromStr for LetterString { - type Err = LetterStringError; - fn from_str(s: &str) -> Result { - s.is_empty() - .not() - .then_some( - s.chars() - .all(|c| c.is_alphabetic()) - .then_some(s) - .map(str::to_string) - .map(Self) - .ok_or(Self::Err::String(s.to_string())), - ) - .ok_or(Self::Err::Empty)? - } -} - -/// Parse a single key-value pair -/// -/// From https://github.com/clap-rs/clap/blob/6b12a81bafe7b9d013b06981f520ab4c70da5510/examples/typed-derive.rs -fn parse_key_val( - s: &str, -) -> Result<(T, U), Box> -where - T: std::str::FromStr, - T::Err: Error + Send + Sync + 'static, - U: std::str::FromStr, - U::Err: Error + Send + Sync + 'static, -{ - let pos = s - .find(SEP) - .ok_or_else(|| format!("invalid KEY{SEP}value: no `{SEP}` found in `{s}`"))?; - Ok((s[..pos].parse()?, s[pos + 1..].parse()?)) -} - -fn parse_key_tuple( - s: &str, -) -> Result<(T, (U1, U2)), Box> -where - T: std::str::FromStr, - T::Err: Error + Send + Sync + 'static, - U1: std::str::FromStr, - U1::Err: Error + Send + Sync + 'static, - U2: std::str::FromStr, - U2::Err: Error + Send + Sync + 'static, -{ - let pos = s - .find(':') - .ok_or_else(|| format!("invalid KEY:value, no `:` found in `{s}`"))?; - Ok(( - s[..pos].parse()?, - parse_key_val::<'=', _, _>(&s[pos + 1..])?, - )) -} - -#[derive(Parser, Clone)] -#[cfg_attr(debug_assertions, derive(Debug))] -#[group(required = false, multiple = false)] -struct InputGroup { - /// Set the path to your sheet music file [default: stdin] - input: Option, - /// Use this sheet music instead of reading from a file or stdin - #[arg(short = 'c')] - sheet_music_string: Option, -} - -#[derive(Debug)] -struct ClonableFile(File); - -impl Clone for ClonableFile { - fn clone(&self) -> Self { - Self(self.0.try_clone().expect("cloning file handle")) - } -} - -impl FromStr for ClonableFile { - type Err = io::Error; - fn from_str(s: &str) -> Result { - File::open(s).map(Self) - } -} - -#[derive(Parser, Clone)] -#[cfg_attr(debug_assertions, derive(Debug))] -struct ExportOpts { - #[command(flatten)] - playopts: PlayOpts, - /// Audio format to use - #[cfg_attr( - feature = "mp3", - arg(default_value = "mp3 --bitrate 128 --quality best") - )] - #[cfg_attr( - not(feature = "mp3"), - cfg_attr(feature = "raw", arg(default_value = "raw mulaw")) - )] - #[arg(short, long, value_parser = audio_format_parser)] - format: AudioFormat, - /// Output file [default: stdout] - output: Option, -} - -#[derive(Clone, EnumDiscriminants)] -#[strum_discriminants(derive(EnumString, IntoStaticStr), strum(serialize_all = "lowercase"))] -enum AudioFormat { - Mp3 { - bitrate: Bitrate, - quality: Quality, - }, - Wav { - bps: u16, - sample_format: SampleFormat, - }, - Flac { - bps: usize, - }, - Raw(RawAudioFormat), -} - -#[cfg(debug_assertions)] -impl Debug for AudioFormat { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Raw(r) => f.debug_tuple("Raw").field(r).finish(), - _ => self.discriminant().fmt(f), - } - } -} - -impl Default for AudioFormat { - fn default() -> Self { - AudioFormat::Raw(Default::default()) - } -} - -fn audio_format_parser(input: &str) -> Result { - fn mp3<'a>() -> impl P< - LocatedSpan<&'a str>, - Output = AudioFormat, - Error = nom::error::Error>, - > { - preceded( - tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error>>( - AudioFormatDiscriminants::Mp3.into(), - ), - u16.or(success(320)) - .and( - alt(( - value(Quality::Best, tag("b")), - value(Quality::SecondBest, tag("sb")), - value(Quality::NearBest, tag("nb")), - value(Quality::VeryNice, tag("v")), - value(Quality::Nice, tag("n")), - value(Quality::Good, tag("g")), - value(Quality::Decent, tag("d")), - value(Quality::Ok, tag("o")), - value(Quality::SecondWorst, tag("sw")), - value(Quality::Worst, tag("w")), - )) - .or(success(Quality::Good)), - ) - .and(position) - .map_res(|((b, q), p)| { - Ok(AudioFormat::Mp3 { - bitrate: match b { - 8 => Bitrate::Kbps8, - 16 => Bitrate::Kbps16, - 24 => Bitrate::Kbps24, - 32 => Bitrate::Kbps32, - 40 => Bitrate::Kbps40, - 48 => Bitrate::Kbps48, - 64 => Bitrate::Kbps64, - 80 => Bitrate::Kbps80, - 96 => Bitrate::Kbps96, - 112 => Bitrate::Kbps112, - 128 => Bitrate::Kbps128, - 160 => Bitrate::Kbps160, - 192 => Bitrate::Kbps192, - 224 => Bitrate::Kbps224, - 256 => Bitrate::Kbps256, - 320 => Bitrate::Kbps320, - _ => return Err(nom::error::Error::new(p, ErrorKind::Verify)), - }, - quality: q, - }) - }), - ) - } - fn wav<'a>() -> impl P< - LocatedSpan<&'a str>, - Output = AudioFormat, - Error = nom::error::Error>, - > { - preceded( - tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error>>( - AudioFormatDiscriminants::Wav.into(), - ), - u16.or(success(320)) - .and(value(SampleFormat::Float, char('f')).or(success(SampleFormat::Int))) - .map(|(bps, sample_format)| AudioFormat::Wav { bps, sample_format }), - ) - } - fn flac<'a>() -> impl P< - LocatedSpan<&'a str>, - Output = AudioFormat, - Error = nom::error::Error>, - > { - preceded( - tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error>>( - AudioFormatDiscriminants::Flac.into(), - ), - usize - .or(success(320000)) - .map(|bps| AudioFormat::Flac { bps }), - ) - } - fn parser<'a>() -> impl P< - LocatedSpan<&'a str>, - Output = AudioFormat, - Error = nom::error::Error>, - > { - alt(( - mp3::<'a>(), - wav::<'a>(), - flac::<'a>(), - rest.map_res(|r: LocatedSpan<&'a str>| { - Ok::>>(AudioFormat::Raw( - RawAudioFormat::try_from(*r) - .map_err(|_| nom::error::Error::new(r, ErrorKind::Verify))?, - )) - }), - )) - } - parser() - .parse_complete(LocatedSpan::new(input)) - .finish() - .map(|(_, o)| o) - .map_err(|e| anyhow!("{e:?}")) -} - -#[derive(Parser, Clone, EnumString, Default)] -#[cfg_attr(debug_assertions, derive(Debug))] -#[strum(ascii_case_insensitive)] -enum RawAudioFormat { - ALaw, - F32Be, - F32Le, - F64Be, - F64Le, - #[default] - MuLaw, - S8, - S16Be, - S16Le, - S24Be, - S24Le, - S32Be, - S32Le, - U8, - U16Be, - U16Le, - U24Be, - U24Le, - U32Be, - U32Le, -} - -#[derive(Parser, Clone)] -#[cfg_attr(debug_assertions, derive(Debug))] -enum Memo { - Syntax, - #[command(subcommand)] - Example(Example), - Formats, -} - -#[derive(Parser, Clone)] -#[cfg_attr(debug_assertions, derive(Debug))] -enum Example { - List, - N { id: u8 }, -}