From a372937854719069387e84bb57ff3a9022325f09 Mon Sep 17 00:00:00 2001 From: Elias Wendland <193786789+eliaswen@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:26:49 +0200 Subject: [PATCH] feat: add --list-formats command, support auto-generated man pages --- Cargo.toml | 13 ++++++++++++- build.rs | 14 ++++++++++++++ src/args.rs | 12 ++++++++---- src/main.rs | 42 ++++++++++++++++++++++++++++++++++++------ 4 files changed, 70 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e578ab6..dea3597 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,15 +24,26 @@ extended-description = "A program attempting to be a universal file converter" depends = "ffmpeg, $auto" section = "utility" priority = "optional" +assets = [ + ["target/release/convertis", "usr/bin/", "755"], + ["README.md", "usr/share/doc/convertis/README", "644"], + ["target/man/convertis.1", "usr/share/man/man1/convertis.1", "644"] +] [package.metadata.generate-rpm] assets = [ - { source = "target/release/convertis", dest = "/usr/bin/convertis", mode = "755" } + { source = "target/release/convertis", dest = "/usr/bin/convertis", mode = "755" }, + { source = "target/man/convertis.1", dest = "/usr/share/man/man1/convertis.1", mode = "644" } ] [package.metadata.generate-rpm.requires] ffmpeg = "*" +[build-dependencies] +clap = { version = "4.6.1", features = ["derive"] } +clap_mangen = "0.2" +tracing = "0.1.44" + [dependencies] clap = { version = "4.6.1", features = ["derive"] } image = "0.25.10" diff --git a/build.rs b/build.rs index 4036f62..95c47c0 100644 --- a/build.rs +++ b/build.rs @@ -16,6 +16,10 @@ use std::env; use std::fs; use std::path::Path; use std::process::Command; +use clap::CommandFactory; + +#[path = "src/args.rs"] +mod args; fn run_command(cmd: &str, args: &[&str]) -> Option { let output = Command::new(cmd).args(args).output().ok()?; @@ -104,4 +108,14 @@ fn main() { let version_path = Path::new(&out_dir).join("version.txt"); fs::write(&version_path, &version_string).unwrap(); + + // Generate man page + let man_dir = Path::new("target").join("man"); + fs::create_dir_all(&man_dir).unwrap(); + let mut cmd = args::Args::command(); + cmd = cmd.name("convertis").version(env!("CARGO_PKG_VERSION")); + let man = clap_mangen::Man::new(cmd); + let mut buffer: Vec = Default::default(); + man.render(&mut buffer).unwrap(); + fs::write(man_dir.join("convertis.1"), buffer).unwrap(); } diff --git a/src/args.rs b/src/args.rs index 08b9c96..2175857 100644 --- a/src/args.rs +++ b/src/args.rs @@ -15,11 +15,15 @@ use clap::Parser; use std::path::PathBuf; -pub const VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/version.txt")); - #[derive(Parser, Debug)] -#[command(version = VERSION, about = "A file converter program.", long_about = None)] +#[command(about = "A file converter program.", long_about = None)] pub struct Args { + #[arg( + long, + help = "List all supported formats.", + exclusive = true + )] + pub list_formats: bool, #[arg( short = 'v', long, @@ -58,7 +62,7 @@ pub struct Args { pub priority: String, #[arg(help = "The input file path.")] - pub input_path: String, + pub input_path: Option, #[arg(help = "The output file path (optional).")] pub output_path: Option, diff --git a/src/main.rs b/src/main.rs index 7a82e6a..f9a0e17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,13 +21,18 @@ pub mod runner; include!(concat!(env!("OUT_DIR"), "/plugins_gen.rs")); use args::Args; -use clap::Parser; +use clap::{Parser, CommandFactory, FromArgMatches}; use std::fs; use std::io::{self, IsTerminal, Write}; use std::process; +pub const VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/version.txt")); + fn main() { - let args = Args::parse(); + let mut command = Args::command(); + command = command.version(VERSION); + let matches = command.get_matches(); + let args = Args::from_arg_matches(&matches).expect("Failed to parse arguments"); let level_filter = if args.quiet { tracing_subscriber::filter::LevelFilter::OFF @@ -39,8 +44,35 @@ fn main() { .with_max_level(level_filter) .init(); + let plugins = get_plugins(); + + if args.list_formats { + let mut formats: std::collections::HashSet<&str> = std::collections::HashSet::new(); + for p in &plugins { + formats.extend(p.from_formats()); + formats.extend(p.to_formats()); + } + let mut formats_vec: Vec<_> = formats.into_iter().collect(); + formats_vec.sort(); + + println!("Supported Formats:"); + for chunk in formats_vec.chunks(6) { + let row = chunk.iter().map(|s| format!("{:<10}", s)).collect::>().join(" "); + println!(" {}", row); + } + return; + } + + let input_path = match &args.input_path { + Some(path) => path, + None => { + tracing::error!("Input file path is required unless --list-formats is used."); + process::exit(1); + } + }; + // Read input file - let input_bytes = match fs::read(&args.input_path) { + let input_bytes = match fs::read(input_path) { Ok(b) => b, Err(e) => { tracing::error!("Error reading input file: {}", e); @@ -48,9 +80,7 @@ fn main() { } }; - let plugins = get_plugins(); - - let from_format = match identifier::identify_format(&args.input_path, &plugins) { + let from_format = match identifier::identify_format(input_path, &plugins) { Some(f) => f, None => { tracing::error!("Unknown input format.");