s3find 0.16.1

A command line utility to walk an Amazon S3 hierarchy. s3find is an analog of find for Amazon S3.
Documentation
use std::io::Write;
use std::process::Command;

use anyhow::{Error, anyhow};
use async_trait::async_trait;

use crate::adapters::aws::CommandS3Client;
use crate::arg::{Exec, S3Path};
use crate::command::StreamObject;
use crate::error::FunctionError;

use super::{ExecStatus, RunCommand};

impl Exec {
    #[inline]
    pub(crate) fn exec<I: Write>(&self, io: &mut I, key: &str) -> Result<ExecStatus, Error> {
        let command_str = self.utility.replace("{}", key);
        let split = shlex::split(&command_str).ok_or(FunctionError::CommandlineParse)?;
        let (command_name, command_args) =
            split.split_first().ok_or(FunctionError::CommandlineParse)?;

        let mut command = Command::new(command_name);
        command.args(command_args);

        let output = command.output()?;
        if !output.status.success() {
            let status = output
                .status
                .code()
                .map(|code| code.to_string())
                .unwrap_or_else(|| "terminated by signal".to_string());
            let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
            let message = if stderr.is_empty() {
                format!("command `{command_str}` failed with status {status}")
            } else {
                format!("command `{command_str}` failed with status {status}: {stderr}")
            };
            return Err(anyhow!(message));
        }

        let output_str = String::from_utf8_lossy(&output.stdout);
        writeln!(io, "{}", &output_str)?;

        Ok(ExecStatus {
            status: output.status,
            runcommand: command_str,
        })
    }
}

#[async_trait]
impl RunCommand for Exec {
    async fn execute(
        &self,
        _: &dyn CommandS3Client,
        path: &S3Path,
        list: &[StreamObject],
    ) -> Result<(), Error> {
        let mut stdout = std::io::stdout();
        for x in list {
            let s3_path = format!("s3://{}/{}", &path.bucket, x.display_key());
            self.exec(&mut stdout, &s3_path)?;
        }
        Ok(())
    }
}