diff --git a/Cargo.toml b/Cargo.toml index a4078dd84..45d18e36d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,11 @@ keywords = ["audio", "codec", "flac"] [dependencies] -docopt = "^0.6.78" hound = "^1.1.0" nom = "^1.0.0" rustc-serialize = "^0.3.16" [dev-dependencies] +clap = { version = "^4.5.39", features = ["derive"] } rust-crypto = "^0.2.34" diff --git a/examples/commands/comments.rs b/examples/commands/comments.rs index 550495ba3..943151d8f 100644 --- a/examples/commands/comments.rs +++ b/examples/commands/comments.rs @@ -1,32 +1,28 @@ use std::io::{self, Write}; use std::fs::File; +use clap::Args; + use flac::StreamReader; use flac::metadata::{self, VorbisComment}; -pub const USAGE: &'static str = " -Usage: metadata comments [options] - metadata comments --help - -Options: - --vendor Show the vendor string. - --name=NAME Show the comments matching the `NAME`. - --export=FILE Export to file. - -h, --help Show this message. -"; - -#[derive(Debug, RustcDecodable)] +#[derive(Args)] pub struct Arguments { - arg_filename: String, - flag_vendor: bool, - flag_name: Option, - flag_export: Option, + #[arg(short, long)] + filename: String, + #[arg(short, long)] + vendor: bool, + #[arg(short, long, value_name = "NAME")] + name: Option, + #[arg(short, long, value_name = "FILE")] + export: Option, } + fn print_vorbis_comments(vorbis_comment: &VorbisComment, args: &Arguments) { - let no_flags = (args.flag_vendor || args.flag_name.is_some()) == false; + let no_flags = (args.vendor || args.name.is_some()) == false; - if no_flags || args.flag_vendor { + if no_flags || args.vendor { format_print!("{}{}", "Vendor string: ", vorbis_comment.vendor_string, no_flags); } @@ -42,7 +38,7 @@ fn print_vorbis_comments(vorbis_comment: &VorbisComment, args: &Arguments) { index += 1; } } else { - if let Some(ref name) = args.flag_name { + if let Some(ref name) = args.name { let error_str = format!("Couldn't find tag name: \"{}\"", name); let result = vorbis_comment.comments.get(name).unwrap_or(&error_str); @@ -63,13 +59,13 @@ fn export_vorbis_comments(vorbis_comment: &VorbisComment, filename: &str) } pub fn run(args: &Arguments) { - let stream = StreamReader::::from_file(&args.arg_filename) + let stream = StreamReader::::from_file(&args.filename) .expect("Couldn't parse file"); for meta in stream.metadata() { match meta.data { metadata::Data::VorbisComment(ref v) => { - if let Some(ref filename) = args.flag_export { + if let Some(ref filename) = args.export { export_vorbis_comments(v, filename) .expect("couldn't write to file") } else { diff --git a/examples/commands/picture.rs b/examples/commands/picture.rs index 338d1f6fe..c5515cbf0 100644 --- a/examples/commands/picture.rs +++ b/examples/commands/picture.rs @@ -1,24 +1,19 @@ use std::io::{self, Write}; use std::fs::File; +use clap::Args; + use flac::stream::StreamReader; use flac::metadata::{self, Picture}; -pub const USAGE: &'static str = " -Usage: metadata picture [options] - metadata picture --help - -Options: - --export=FILE Export to file. - --index=NUMBER Index of the current metadata type. - -h, --help Show this message. -"; - -#[derive(Debug, RustcDecodable)] +#[derive(Args)] pub struct Arguments { - arg_filename: String, - flag_export: Option, - flag_index: Option, + #[arg(short, long, required = true, value_name = "FILE")] + filename: String, + #[arg(short, long, value_name = "FILE")] + export: Option, + #[arg(short, long)] + index: Option, } fn export_picture(picture: &Picture, filename: &str) -> io::Result<()> { @@ -36,11 +31,11 @@ fn print_picture(picture: &Picture) { } pub fn run(args: &Arguments) { - let stream = StreamReader::::from_file(&args.arg_filename) + let stream = StreamReader::::from_file(&args.filename) .expect("Couldn't parse file"); let mut index = 0; - let end_index = args.flag_index.unwrap_or(0); + let end_index = args.index.unwrap_or(0); for meta in stream.metadata() { match meta.data { @@ -51,7 +46,7 @@ pub fn run(args: &Arguments) { continue; } - if let Some(ref filename) = args.flag_export { + if let Some(ref filename) = args.export { export_picture(p, filename).expect("couldn't write to file"); break; diff --git a/examples/commands/seektable.rs b/examples/commands/seektable.rs index e1a728f95..b6205c2cb 100644 --- a/examples/commands/seektable.rs +++ b/examples/commands/seektable.rs @@ -1,19 +1,14 @@ use std::fs::File; +use clap::Args; + use flac::StreamReader; use flac::metadata::{self, SeekPoint}; -pub const USAGE: &'static str = " -Usage: metadata seektable - metadata seektable --help - -Options: - -h, --help Show this message. -"; - -#[derive(Debug, RustcDecodable)] +#[derive(Args)] pub struct Arguments { - arg_filename: String, + #[arg(short, long, value_name = "FILE")] + filename: String, } fn print_seek_table(seek_points: &[SeekPoint]) { @@ -31,7 +26,7 @@ fn print_seek_table(seek_points: &[SeekPoint]) { } pub fn run(args: &Arguments) { - let stream = StreamReader::::from_file(&args.arg_filename) + let stream = StreamReader::::from_file(&args.filename) .expect("Couldn't parse file"); for meta in stream.metadata() { diff --git a/examples/commands/streaminfo.rs b/examples/commands/streaminfo.rs index d973e0b04..2fa4e21cd 100644 --- a/examples/commands/streaminfo.rs +++ b/examples/commands/streaminfo.rs @@ -1,43 +1,37 @@ use std::fs::File; +use clap::Args; use flac::{Stream, StreamProducer, StreamReader}; -pub const USAGE: &'static str = " -Usage: metadata streaminfo [options] - metadata streaminfo --help - -Options: - --block-size Show both the max and min block size from StreamInfo. - --frame-size Show both the max and min frame size from StreamInfo. - --sample-rate Show the sample rate from StreamInfo. - --channels Show the number of channels from StreamInfo. - --bits-per-sample Show the size in bits for each sample from StreamInfo. - --total-samples Show total number of samples from StreamInfo. - --md5 Show the MD5 signature from StreamInfo. - -h, --help Show this message. -"; - -#[derive(Debug, RustcDecodable)] +#[derive(Args)] pub struct Arguments { - arg_filename: String, - flag_block_size: bool, - flag_frame_size: bool, - flag_sample_rate: bool, - flag_channels: bool, - flag_bits_per_sample: bool, - flag_total_samples: bool, - flag_md5: bool, + #[arg(short, long, required = true)] + filename: String, + #[arg(long)] + block_size: bool, + #[arg(long)] + frame_size: bool, + #[arg(long)] + sample_rate: bool, + #[arg(long)] + channels: bool, + #[arg(long)] + bits_per_sample: bool, + #[arg(long)] + total_samples: bool, + #[arg(long)] + md5: bool, } fn print_stream_info

(stream: &Stream

, args: &Arguments) where P: StreamProducer { let info = stream.info(); - let no_flags = (args.flag_block_size || args.flag_frame_size || - args.flag_sample_rate || args.flag_channels || - args.flag_bits_per_sample || args.flag_total_samples || - args.flag_md5) == false; + let no_flags = (args.block_size || args.frame_size || + args.sample_rate || args.channels || + args.bits_per_sample || args.total_samples || + args.md5) == false; - if no_flags || args.flag_block_size { + if no_flags || args.block_size { let block_size_str = if info.is_fixed_block_size() { format!("{} samples", info.max_block_size) } else { @@ -47,29 +41,29 @@ fn print_stream_info

(stream: &Stream

, args: &Arguments) format_print!("{}{}", "Block size: ", block_size_str, no_flags); } - if no_flags || args.flag_frame_size { + if no_flags || args.frame_size { println!("Frame size: {} - {} bytes", info.min_frame_size, info.max_frame_size); } - if no_flags || args.flag_sample_rate { + if no_flags || args.sample_rate { format_print!("{}{} Hz", "Sample rate: ", info.sample_rate, no_flags); } - if no_flags || args.flag_channels { + if no_flags || args.channels { format_print!("{}{}", "Number of channels: ", info.channels, no_flags); } - if no_flags || args.flag_bits_per_sample { + if no_flags || args.bits_per_sample { format_print!("{}{}", "Bits per samples: ", info.bits_per_sample, no_flags); } - if no_flags || args.flag_total_samples { + if no_flags || args.total_samples { format_print!("{}{}", "Total samples: ", info.total_samples, no_flags); } - if no_flags || args.flag_md5 { + if no_flags || args.md5 { let mut md5 = String::with_capacity(32); for byte in &info.md5_sum { @@ -83,7 +77,7 @@ fn print_stream_info

(stream: &Stream

, args: &Arguments) } pub fn run(args: &Arguments) { - let stream = StreamReader::::from_file(&args.arg_filename) + let stream = StreamReader::::from_file(&args.filename) .expect("Couldn't parse file"); print_stream_info(&stream, &args); diff --git a/examples/commands/utility.rs b/examples/commands/utility.rs index 3cfd61e30..49c81cac9 100644 --- a/examples/commands/utility.rs +++ b/examples/commands/utility.rs @@ -14,18 +14,6 @@ macro_rules! format_print ( ); ); -macro_rules! command ( - ($name: ident) => ( - { - let args: $name::Arguments = Docopt::new($name::USAGE) - .and_then(|d| d.argv(env::args()).decode()) - .unwrap_or_else(|e| e.exit()); - - $name::run(&args) - } - ); -); - pub fn list_block_names(filename: &str) { let stream = StreamReader::::from_file(filename) .expect("Couldn't parse file"); diff --git a/examples/decode.rs b/examples/decode.rs index 54cef130d..af625c287 100644 --- a/examples/decode.rs +++ b/examples/decode.rs @@ -1,9 +1,8 @@ -extern crate docopt; +extern crate clap; extern crate flac; extern crate hound; extern crate rustc_serialize; -use docopt::Docopt; use flac::{ErrorKind, StreamReader}; use std::env; @@ -11,20 +10,14 @@ use std::fs; use std::io; use std::path::{Path, PathBuf}; -const USAGE: &'static str = " -Usage: decode - decode ...

- decode --help - -Options: - -h, --help Show this message. -"; - -#[derive(RustcDecodable)] +use clap::{Parser, Subcommand}; +#[derive(Parser)] +#[command(version, about, long_about = None)] struct Arguments { - arg_input: Vec, - arg_output: Option, - arg_dir: Option, + #[arg(short, long, required = true, num_args = 1.., value_delimiter = ' ', value_name = "FILE")] + input: Vec, + #[arg(short, long, required = true, value_name = "FILE/DIR")] + output: String, } fn to_io_error(kind: ErrorKind) -> hound::Error { @@ -124,21 +117,19 @@ fn decode_all_files(input_files: &Vec, directory: &str) } fn main() { - let args: Arguments = Docopt::new(USAGE) - .and_then(|d| d.argv(env::args()).decode()) - .unwrap_or_else(|e| e.exit()); + let args = Arguments::parse(); - if let Some(ref output_file) = args.arg_output { - let input_file = args.arg_input.get(0) - .expect("No input file"); + if args.input.len() == 1 { + let input_file = args.input.get(0) + .expect("No input file"); - if let Err(e) = decode_file(input_file, output_file) { + if let Err(e) = decode_file(input_file, &args.output) { println!("{:?}", e); } else { - println!("decoded: {} -> {}", input_file, output_file); + println!("decoded: {} -> {}", input_file, args.output); } - } else if let Some(ref directory) = args.arg_dir { - if let Err(e) = decode_all_files(&args.arg_input, directory) { + } else { + if let Err(e) = decode_all_files(&args.input, &args.output) { println!("{:?}", e); } } diff --git a/examples/metadata.rs b/examples/metadata.rs index 4b107e2c0..09f9c5b59 100644 --- a/examples/metadata.rs +++ b/examples/metadata.rs @@ -1,67 +1,40 @@ -extern crate docopt; +extern crate clap; extern crate flac; extern crate rustc_serialize; #[macro_use] mod commands; -use std::env; - use commands::{streaminfo, comments, seektable, picture, list_block_names}; -use docopt::Docopt; - -const USAGE: &'static str = " -Usage: metadata [...] - metadata [options] [] - -Options: - --list List all blocks by name. - -h, --help Show this message. - -Commands: - streaminfo Display stream information. - comments Display or export comment tags. - seektable Display seek table. - picture Export pictures. -"; -#[derive(Debug, RustcDecodable)] +use clap::{Parser, Subcommand}; +#[derive(Parser)] +#[command(version, about, long_about = None)] struct Arguments { - arg_command: Option, - arg_filename: Option, - flag_list: bool, - arg_args: Vec, + #[command(subcommand)] + command: Command, } -#[derive(Clone, Copy, Debug, RustcDecodable)] +#[derive(Subcommand)] enum Command { - StreamInfo, - Comments, - SeekTable, - Picture, -} - -fn handle_subcommand(command: Command) { - match command { - Command::StreamInfo => command!(streaminfo), - Command::Comments => command!(comments), - Command::SeekTable => command!(seektable), - Command::Picture => command!(picture), - } + List { + #[arg(short, long)] + filename: String, + }, + StreamInfo(streaminfo::Arguments), + Comments(comments::Arguments), + SeekTable(seektable::Arguments), + Picture(picture::Arguments), } fn main() { - let args: Arguments = Docopt::new(USAGE) - .and_then(|d| d.options_first(true).decode()) - .unwrap_or_else(|e| e.exit()); - - if let Some(command) = args.arg_command { - handle_subcommand(command); - } else if let Some(ref filename) = args.arg_filename { - if args.flag_list { - list_block_names(filename); - } - } else { - println!("{}", USAGE); + let args = Arguments::parse(); + + match args.command { + Command::List { filename } => list_block_names(&filename), + Command::StreamInfo(stream_info) => streaminfo::run(&stream_info), + Command::Comments(comments) => comments::run(&comments), + Command::SeekTable(seek_table) => seektable::run(&seek_table), + Command::Picture(picture) => picture::run(&picture), } }