Shell completions and refactors

Output shell completions with `clap_complete`, moving the cli args types into a new file `cli_args.rs`, and add them to the nix package defined in the flake output. Update clap to 4.5.21. Various minor refactors in main.rs, including a removal of `itertools` as a dependency.
This commit is contained in:
Skye 2024-11-14 15:38:01 -05:00
parent 5be313863e
commit f9df43f5e7
6 changed files with 100 additions and 69 deletions

34
Cargo.lock generated
View file

@ -53,9 +53,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.20" version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -63,9 +63,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.20" version = "4.5.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -73,6 +73,15 @@ dependencies = [
"strsim", "strsim",
] ]
[[package]]
name = "clap_complete"
version = "4.5.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01"
dependencies = [
"clap",
]
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.18" version = "4.5.18"
@ -97,12 +106,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@ -115,15 +118,6 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.89" version = "1.0.89"
@ -153,7 +147,7 @@ name = "subtitle-merge"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"itertools", "clap_complete",
] ]
[[package]] [[package]]

View file

@ -4,5 +4,8 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
clap = { version = "4.5.20", features = ["derive"] } clap = { version = "4.5.21", features = ["derive"] }
itertools = "0.13.0"
[build-dependencies]
clap = { version = "4.5.21", features = ["derive"] }
clap_complete = "4.5.38"

22
build.rs Normal file
View file

@ -0,0 +1,22 @@
use std::{env, io};
use clap_complete::{generate_to, Shell};
use cli_args::Args;
use clap::{CommandFactory, ValueEnum};
mod cli_args {
include!("src/cli_args.rs");
}
fn main() -> io::Result<()> {
let Some(outdir) = env::var_os("OUT_DIR") else {
return Ok(());
};
let mut command = Args::command();
for shell in Shell::value_variants() {
generate_to(*shell, &mut command, "subtitle-merge", &outdir)?;
// println!("cargo:warning=completion file is generated: {path:?}");
}
Ok(())
}

View file

@ -18,15 +18,24 @@
overlays.default = final: prev: { overlays.default = final: prev: {
subtitle-merge = final.rustPlatform.buildRustPackage { subtitle-merge = final.rustPlatform.buildRustPackage {
name = "subtitle-merge"; name = "subtitle-merge";
version = "1.0"; version = "0.1";
src = ./.; src = ./.;
cargoLock.lockFile = ./Cargo.lock; cargoLock.lockFile = ./Cargo.lock;
buildInputs = [ final.mkvtoolnix-cli ]; buildInputs = builtins.attrValues {
inherit (final) installShellFiles mkvtoolnix-cli;
};
postInstall = ''
installShellCompletion --cmd subtitle-merge \
--bash ./target/release-tmp/build/*/out/subtitle-merge.bash \
--fish ./target/release-tmp/build/*/out/subtitle-merge.fish \
--zsh ./target/release-tmp/build/*/out/_subtitle-merge
'';
}; };
}; };
packages."${system}" = rec { packages."${system}" = rec {
subtitle-merge = (overlays.default pkgs pkgs).subtitle-merge; inherit (overlays.default pkgs pkgs) subtitle-merge;
default = subtitle-merge; default = subtitle-merge;
}; };
}; };

25
src/cli_args.rs Normal file
View file

@ -0,0 +1,25 @@
use std::path::PathBuf;
use clap::Parser;
#[derive(Parser)]
pub enum Args {
Single(Single),
Dir(Dir),
}
#[derive(Parser)]
pub struct Single {
pub source: PathBuf,
#[arg(long)]
pub subtitles: Option<PathBuf>,
#[arg(long)]
pub fonts_dir: Option<PathBuf>,
pub output: PathBuf,
}
#[derive(Parser)]
pub struct Dir {
pub input_dir: PathBuf,
pub output_dir: PathBuf,
}

View file

@ -1,36 +1,16 @@
mod cli_args;
use std::{ use std::{
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
fs::ReadDir, fs::ReadDir,
io, io,
os::unix::{ffi::OsStrExt, process::CommandExt}, os::unix::{ffi::OsStrExt, process::CommandExt},
path::{Path, PathBuf}, path::{Path, PathBuf},
process::{Command, ExitCode, ExitStatus}, process::{Command, ExitCode},
}; };
use clap::Parser; use clap::Parser;
use itertools::Either; use cli_args::{Args, Single};
#[derive(Parser)]
enum Args {
Single(Single),
Dir(Dir),
}
#[derive(Parser)]
struct Single {
source: PathBuf,
#[arg(long)]
subtitles: Option<PathBuf>,
#[arg(long)]
fonts_dir: Option<PathBuf>,
output: PathBuf,
}
#[derive(Parser)]
struct Dir {
input_dir: PathBuf,
output_dir: PathBuf,
}
fn find_subs(root: &Path, match_str: &OsStr) -> io::Result<Option<PathBuf>> { fn find_subs(root: &Path, match_str: &OsStr) -> io::Result<Option<PathBuf>> {
for entry in root.read_dir()? { for entry in root.read_dir()? {
@ -59,7 +39,10 @@ fn find_subs(root: &Path, match_str: &OsStr) -> io::Result<Option<PathBuf>> {
Ok(None) Ok(None)
} }
fn find_files(root: &Path, extension: &OsStr) -> impl Iterator<Item = io::Result<PathBuf>> { fn find_files(
root: &Path,
extension: &OsStr,
) -> io::Result<impl Iterator<Item = io::Result<PathBuf>>> {
struct FindFonts { struct FindFonts {
read_dirs: Vec<ReadDir>, read_dirs: Vec<ReadDir>,
extension: OsString, extension: OsString,
@ -74,11 +57,10 @@ fn find_files(root: &Path, extension: &OsStr) -> impl Iterator<Item = io::Result
continue; continue;
}; };
let entry = match next { let entry = match next {
Err(e) => return Some(Err(e)),
Ok(entry) => entry, Ok(entry) => entry,
Err(e) => return Some(Err(e)),
}; };
let file_type = entry.file_type(); let file_type = match entry.file_type() {
let file_type = match file_type {
Err(e) => return Some(Err(e)), Err(e) => return Some(Err(e)),
Ok(file_type) => file_type, Ok(file_type) => file_type,
}; };
@ -101,14 +83,10 @@ fn find_files(root: &Path, extension: &OsStr) -> impl Iterator<Item = io::Result
} }
} }
} }
Ok(FindFonts {
match root.read_dir() { read_dirs: vec![root.read_dir()?],
Ok(read_dir) => Either::Left(FindFonts { extension: extension.to_owned(),
read_dirs: vec![read_dir], })
extension: extension.to_owned(),
}),
Err(e) => Either::Right(std::iter::once(Err(e))),
}
} }
fn convert_single( fn convert_single(
@ -119,18 +97,18 @@ fn convert_single(
output, output,
}: Single, }: Single,
) -> io::Result<Command> { ) -> io::Result<Command> {
let subtitles = if let Some(subtitles) = subtitles { let subtitles = subtitles.map_or_else(|| {
subtitles find_subs(source.parent().unwrap(), source.file_stem().unwrap())
} else { .transpose()
find_subs(&source.parent().unwrap(), source.file_stem().unwrap())?.unwrap() .unwrap()
}; }, Ok)?;
let fonts: Vec<PathBuf> = if let Some(fonts_dir) = fonts_dir { let fonts: Vec<PathBuf> = if let Some(fonts_dir) = fonts_dir {
fonts_dir fonts_dir
.read_dir()? .read_dir()?
.map(|r| r.map(|entry| entry.path())) .map(|r| r.map(|entry| entry.path()))
.collect::<io::Result<_>>()? .collect::<io::Result<_>>()?
} else { } else {
find_files(source.parent().unwrap(), OsStr::new("ttf")).collect::<io::Result<_>>()? find_files(source.parent().unwrap(), OsStr::new("ttf"))?.collect::<io::Result<_>>()?
}; };
let mut command = Command::new("mkvmerge"); let mut command = Command::new("mkvmerge");
@ -141,7 +119,7 @@ fn convert_single(
]); ]);
for font in fonts { for font in fonts {
command command
.args(&["--attachment-mime-type", "font/ttf", "--attach-file"]) .args(["--attachment-mime-type", "font/ttf", "--attach-file"])
.arg(font.into_os_string()); .arg(font.into_os_string());
} }
Ok(command) Ok(command)
@ -155,7 +133,7 @@ fn main() -> io::Result<ExitCode> {
Err(command.exec()) Err(command.exec())
} }
Args::Dir(dir) => { Args::Dir(dir) => {
let files = find_files(&dir.input_dir, OsStr::new("mkv")); let files = find_files(&dir.input_dir, OsStr::new("mkv"))?;
let mut commands = Vec::new(); let mut commands = Vec::new();
for file in files { for file in files {
let input_file = file?; let input_file = file?;