acts-package-shell 0.18.0

acts package for shell
Documentation
use acts::{
    ActError, ActPackage, ActPackageCatalog, ActPackageMeta, ActRunAs, Result, Vars, include_json,
};
use serde::{Deserialize, Serialize};
use serde_json::{Value as JsonValue, json};
use std::process::Command;
use strum::AsRefStr;

const DATA_KEY: &str = "data";

#[derive(Debug, Clone, Deserialize, Serialize, AsRefStr)]
pub enum Shell {
    #[serde(rename(deserialize = "sh"))]
    #[strum(serialize = "sh")]
    Sh,
    #[allow(clippy::enum_variant_names)]
    #[serde(rename(deserialize = "nu"))]
    #[strum(serialize = "nu")]
    NuShell,
    #[serde(rename(deserialize = "bash"))]
    #[strum(serialize = "bash")]
    Bash,
    #[allow(clippy::enum_variant_names)]
    #[serde(rename(deserialize = "powershell"))]
    #[strum(serialize = "powershell")]
    PowerShell,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum ContentType {
    #[serde(rename(deserialize = "text"))]
    Text,
    #[serde(rename(deserialize = "json"))]
    Json,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ShellPackage {
    shell: Option<Shell>,
    script: String,
    #[serde(rename(deserialize = "content-type"))]
    content_type: Option<ContentType>,
}

impl ActPackage for ShellPackage {
    fn meta() -> ActPackageMeta {
        ActPackageMeta {
            id: "acts.app.shell",
            name: "Shell",
            desc: "do shell script with nushell, bash or powershell",
            version: "0.1.0",
            icon: r#"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-chevron-right-icon lucide-square-chevron-right"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="m10 8 4 4-4 4"/></svg>"#,
            doc: "",
            in_schema: include_json!("./schema.json"),
            ui_schema: Some(json!({
                "ui:order": ["shell", "script", "content-type"],
                "script": {
                    "ui:widget": "textarea",
                },
            })),
            run_as: ActRunAs::Irq,
            resources: vec![],
            catalog: ActPackageCatalog::App,
        }
    }
}

impl ShellPackage {
    pub fn create(inputs: &Vars) -> Result<Self> {
        let params = inputs
            .get::<serde_json::Value>("params")
            .ok_or(ActError::Package("missing 'params' in package".to_string()))?;
        let package = serde_json::from_value::<Self>(params)?;
        Ok(package)
    }

    pub fn run(&self) -> Result<Vars> {
        let mut ret = Vars::new();
        let shell = self.shell.as_ref().unwrap_or(&Shell::Sh);
        let output = Command::new(shell.as_ref())
            .arg("-c")
            .arg(&self.script)
            .output()
            .map_err(|err| ActError::Package(format!("{err}")))?;

        if !output.status.success() {
            let err = String::from_utf8(output.stderr)?;
            return Err(ActError::Package(err));
        }
        let data = String::from_utf8(output.stdout)?;
        let content_type = self.content_type.as_ref().unwrap_or(&ContentType::Text);
        match content_type {
            ContentType::Text => ret.set(DATA_KEY, data),
            ContentType::Json => ret.set(
                DATA_KEY,
                serde_json::from_str::<JsonValue>(&data).map_err(|err| {
                    ActError::Package(format!("failed to convert data to json: {err}"))
                })?,
            ),
        }

        Ok(ret)
    }
}