sops-batch 0.4.0

SOPS encryption / decryption batch tool
use serde::Deserialize;
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use which::which;

pub fn check_readable_file(path: &Path) -> Result<&Path, String> {
    if !path.is_file() {
        return Err("cannot read file.".to_string());
    }
    Ok(path)
}

#[derive(Deserialize)]
pub struct FilesConfig {
    pub files: Vec<String>,
}

pub struct FilePair {
    pub enc_name: String,
    pub dec_name: String,
}

pub fn load_files_config(path: &Path) -> Result<FilesConfig, String> {
    let path = check_readable_file(path)?;
    let contents = match fs::read_to_string(path) {
        Ok(contents) => contents,
        Err(err) => return Err(format!("failed to read file: {}", err)),
    };
    match toml::from_str(&contents) {
        Ok(config) => Ok(config),
        Err(err) => return Err(format!("failed to decode TOML: {}", err)),
    }
}

pub fn find_sops(binary_name: &str) -> Result<PathBuf, String> {
    match which(binary_name) {
        Ok(b) => Ok(b),
        Err(err) => return Err(format!("could not find sops binary: {}", err)),
    }
}

pub fn call_sops(sops_bin: &Path, args: Vec<&str>, verbose: bool) -> Result<ExitStatus, String> {
    if verbose {
        eprintln!("calling sops: {} {}", sops_bin.display(), args.join(" "));
    }
    let mut c = match Command::new(sops_bin).args(args).spawn() {
        Ok(c) => c,
        Err(err) => return Err(format!("sops call failed: {}", err)),
    };
    match c.wait() {
        Ok(s) => Ok(s),
        Err(err) => return Err(format!("sops call failed: {}", err)),
    }
}

impl FilePair {
    pub fn from_filename(filename: &str) -> FilePair {
        let enc_name;
        let dec_name;
        if filename.contains(".enc") {
            enc_name = filename.to_string();
            dec_name = enc_name.replace(".enc", "");
        } else {
            dec_name = filename.to_string();
            let path = match Path::new(&dec_name).parent() {
                Some(path) => path.to_owned(),
                None => Path::new("").to_owned(),
            };
            let filestem = match Path::new(&dec_name).file_stem() {
                Some(stem) => stem.to_owned(),
                None => OsString::from(""),
            };
            let ext = match Path::new(&dec_name).extension() {
                Some(ext) => format!("enc.{}", ext.to_str().unwrap()),
                None => OsString::from("enc").to_str().unwrap().to_owned(),
            };
            let new_parent = PathBuf::from(path);
            let mut new_name = PathBuf::new();
            new_name.set_file_name(filestem);
            new_name.set_extension(ext);
            enc_name = new_parent.join(new_name).to_str().unwrap().to_owned();
        }
        FilePair { enc_name, dec_name }
    }
}