#![warn(clippy::pedantic)]
#![allow(dead_code)]
use std::{
io::{self, Write},
path::PathBuf,
};
use anyhow::{anyhow, bail, Result};
mod cdrom;
mod video;
use cdrom::AudioMediaSource;
use structopt::StructOpt;
use video::{metadata::MetaData, VideoMediaSource};
#[derive(Debug, StructOpt)]
#[structopt(author)]
pub enum Config {
Rip(RipConfig),
Search(SearchConfig),
}
#[derive(Debug, StructOpt)]
pub struct RipConfig {
#[structopt(parse(from_os_str))]
path: PathBuf,
#[structopt(short, long, parse(from_occurrences))]
pub verbosity: u8,
#[structopt(long)]
tmdb_api_key: Option<String>,
#[structopt(short = "i", long)]
tmdb_id: Option<u32>,
#[structopt(short, long, parse(from_os_str))]
config: Option<PathBuf>,
}
#[derive(Debug, StructOpt)]
pub struct SearchConfig {
#[structopt(short, long, parse(from_occurrences))]
pub verbosity: u8,
#[structopt(long)]
tmdb_api_key: Option<String>,
#[structopt(short, long, parse(from_os_str))]
config: Option<PathBuf>,
#[structopt(short, long)]
id: Option<u32>,
#[structopt(short, long)]
title: Option<String>,
}
fn get_stdout(output: std::process::Output) -> Result<String> {
if !output.status.success() {
let stderr = String::from_utf8(output.stderr)?;
return Err(anyhow!(stderr));
}
Ok(String::from_utf8(output.stdout)?)
}
fn get_stderr(output: std::process::Output) -> Result<String> {
let stderr = String::from_utf8(output.stderr)?;
if !output.status.success() {
return Err(anyhow!(stderr));
}
Ok(stderr)
}
fn capture_group_as_str<'a>(
cap: ®ex::Captures<'a>,
groupname: &'static str,
) -> Result<&'a str> {
cap.name(groupname)
.map(|v| v.as_str())
.ok_or_else(|| anyhow!("could not find capture group `{}`", groupname))
}
trait Rip {
fn rip(&self, config: &RipConfig) -> Result<PathBuf>;
}
pub fn rip(config: &RipConfig) -> Result<PathBuf> {
if !config.path.starts_with("/dev/") {
return Err(anyhow!(
"Unsure how deal with path {}",
config.path.display()
));
}
match config.path.to_str() {
Some("/dev/cdrom") => {
AudioMediaSource::new(&config.path).and_then(|src| src.rip(config))
}
Some(path) => {
VideoMediaSource::new(path).and_then(|src| src.rip(None))
}
_ => Err(anyhow!("Unknown audio source")),
}
}
pub fn search(config: &SearchConfig) -> Result<()> {
let md = match config {
SearchConfig { id: Some(id), .. } => {
MetaData::from_id(*id)?.to_string()
}
SearchConfig {
title: Some(title), ..
} => MetaData::search_by_title(title, 2)?
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join("\n"),
_ => bail!("not sure how to search for item without title or id"),
};
match config.verbosity {
0 => write!(io::stdout(), "{md}")?,
_ => write!(io::stdout(), "{md:?}")?,
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::process::Command;
use super::*;
#[test]
fn test_get_stdout() -> Result<()> {
let output_works =
get_stdout(Command::new("echo").arg("foo bar").output()?)?;
assert_eq!(output_works.trim(), "foo bar");
let command_fails = get_stdout(
Command::new("ls").arg("file that doesn't.exist").output()?,
);
assert!(command_fails.is_err());
assert!(command_fails.unwrap_err().to_string().starts_with("ls:"));
Ok(())
}
#[test]
fn test_get_stderr() -> Result<()> {
let output_works = get_stderr(
Command::new("bash")
.arg("-c")
.arg("echo foo bar 1>&2")
.output()?,
)?;
assert_eq!(output_works.trim(), "foo bar");
let command_fails = get_stderr(
Command::new("ls").arg("file that doesn't.exist").output()?,
);
assert!(command_fails.is_err());
assert!(command_fails.unwrap_err().to_string().starts_with("ls:"));
Ok(())
}
}