nu_plugin_rpm 0.3.5

A nushell plugin for reading rpm package.
Documentation
use std::io::{BufReader, Read};

use crate::RpmPlugin;
use nu_plugin::PluginCommand;
use nu_protocol::{record, Category, LabeledError, PipelineData, Signature, Type, Value};
use rpm::{chrono, FileEntry, Scriptlet};

fn dep_to_record(dep: &rpm::Dependency, span: nu_protocol::Span) -> nu_protocol::Value {
    let names = dep
        .flags
        .iter_names()
        .map(|(x, _)| x.to_string())
        .collect::<Vec<String>>();

    Value::record(
        record!(
            "name" => Value::string(dep.name.clone(), span),
            "flags" => Value::string(names.join(" | "), span),
            "version" => Value::string(dep.version.clone(), span),
        ),
        span,
    )
}

fn file_entry_to_record(e: &FileEntry, span: nu_protocol::Span) -> nu_protocol::Value {
    let (typ, mode) = match e.mode {
        rpm::FileMode::Dir { permissions } => ("dir", unix_mode::to_string(permissions as u32)),
        rpm::FileMode::Regular { permissions } => {
            ("file", unix_mode::to_string(permissions as u32))
        }
        rpm::FileMode::SymbolicLink { permissions } => {
            ("symlink", unix_mode::to_string(permissions as u32))
        }
        rpm::FileMode::Invalid { raw_mode, reason } => {
            ("invalid", format!("{} {}", raw_mode, reason))
        }
        _ => ("", "".to_owned()),
    };
    Value::record(
        record!(
            "path" => Value::string(e.path.clone().into_os_string().into_string().unwrap_or_default(), span),
            "type" => Value::string(typ, span),
            "mode" => Value::string(mode, span),
            "user" => Value::string(e.ownership.user.clone(), span),
            "group" => Value::string(e.ownership.group.clone(), span),
            "motified_at" => Value::date(chrono::DateTime::from_timestamp(u32::from(e.modified_at) as i64, 0).unwrap_or_default().into(), span),
            "size" => Value::filesize(e.size as i64, span),
            "linkto" => Value::string(e.linkto.clone(), span),
        ),
        span,
    )
}

pub struct FromRpm;

impl FromRpm {
    fn convert_metadata_to_record(
        &self,
        r: impl Read + Send + 'static,
        span: nu_protocol::Span,
        show_files: bool,
    ) -> Result<nu_protocol::Value, rpm::Error> {
        let mut buf_reader = BufReader::new(r);
        let pkg = rpm::Package::parse(&mut buf_reader)?;
        let md = pkg.metadata;

        let mut rec = record!(
            "name" => Value::string(md.get_name().unwrap_or_default(), span),
            "version" => Value::string(md.get_version().unwrap_or_default(), span),
            "release" => Value::string(md.get_release().unwrap_or_default(), span),
            "arch" => Value::string(md.get_arch().unwrap_or_default(), span),
            "size" => Value::filesize(md.get_installed_size().unwrap_or_default() as i64, span),
            "vendor" => Value::string(md.get_vendor().unwrap_or_default(), span),
            "url" => Value::string(md.get_url().unwrap_or_default(), span),
            "vcs" => Value::string(md.get_vcs().unwrap_or_default(), span),
            "license" => Value::string(md.get_license().unwrap_or_default(), span),
            "summary" => Value::string(md.get_summary().unwrap_or_default(), span),
            "description" => Value::string(md.get_description().unwrap_or_default(), span),
            "group" => Value::string(md.get_group().unwrap_or_default(), span),
            "packager" => Value::string(md.get_packager().unwrap_or_default(), span),
            "build_time" => Value::date(chrono::DateTime::from_timestamp(md.get_build_time().unwrap_or_default() as i64, 0).unwrap_or_default().into(), span),
            "build_host" => Value::string(md.get_build_host().unwrap_or_default(), span),
            "cookie" => Value::string(md.get_cookie().unwrap_or_default(), span),
            "source_rpm" => Value::string(md.get_source_rpm().unwrap_or_default(), span),
            "pre_install_script" => Value::string(md.get_pre_install_script().unwrap_or(Scriptlet::new("")).script, span),
            "post_install_script" => Value::string(md.get_post_install_script().unwrap_or(Scriptlet::new("")).script, span),
            "pre_uninstall_script" => Value::string(md.get_pre_uninstall_script().unwrap_or(Scriptlet::new("")).script, span),
            "post_uninstall_script" => Value::string(md.get_post_uninstall_script().unwrap_or(Scriptlet::new("")).script, span),
            "pre_trans_script" => Value::string(md.get_pre_trans_script().unwrap_or(Scriptlet::new("")).script, span),
            "post_trans_script" => Value::string(md.get_post_trans_script().unwrap_or(Scriptlet::new("")).script, span),
            "pre_untrans_script" => Value::string(md.get_pre_untrans_script().unwrap_or(Scriptlet::new("")).script, span),
            "post_untrans_script" => Value::string(md.get_post_untrans_script().unwrap_or(Scriptlet::new("")).script, span),
            "provides" => Value::list(md.get_provides().unwrap_or_default().iter().map(|x| dep_to_record(x, span)).collect::<Vec<_>>(), span),
            "requires" => Value::list(md.get_requires().unwrap_or_default().iter().map(|x| dep_to_record(x, span)).collect::<Vec<_>>(), span),
            "conficts" => Value::list(md.get_conflicts().unwrap_or_default().iter().map(|x| dep_to_record(x, span)).collect::<Vec<_>>(), span),
            "obsoletes" => Value::list(md.get_obsoletes().unwrap_or_default().iter().map(|x| dep_to_record(x, span)).collect::<Vec<_>>(), span),
            "recommends" => Value::list(md.get_recommends().unwrap_or_default().iter().map(|x| dep_to_record(x, span)).collect::<Vec<_>>(), span),
            "suggests" => Value::list(md.get_suggests().unwrap_or_default().iter().map(|x| dep_to_record(x, span)).collect::<Vec<_>>(), span),
        );
        if show_files {
            rec.insert(
                "files",
                Value::list(
                    md.get_file_entries()
                        .unwrap_or_default()
                        .iter()
                        .map(|e| file_entry_to_record(e, span))
                        .collect(),
                    span,
                ),
            );
        }
        Ok(Value::record(rec, span))
    }
}

impl PluginCommand for FromRpm {
    type Plugin = RpmPlugin;

    fn name(&self) -> &str {
        "from rpm"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .switch(
                "files",
                "Convert file lists from rpm package into table",
                Some('f'),
            )
            .allow_variants_without_examples(true)
            .input_output_types(vec![(Type::Binary, Type::Any)])
            .category(Category::Experimental)
            .filter()
    }

    fn description(&self) -> &str {
        "Convert from rpm file into metadata table"
    }

    fn run(
        &self,
        _plugin: &Self::Plugin,
        _engine: &nu_plugin::EngineInterface,
        call: &nu_plugin::EvaluatedCall,
        input: nu_protocol::PipelineData,
    ) -> Result<nu_protocol::PipelineData, LabeledError> {
        match input {
            PipelineData::ByteStream(byte_stream, pipeline_metadata) => {
                let span = byte_stream.span();
                let rec = self
                    .convert_metadata_to_record(
                        byte_stream
                            .reader()
                            .ok_or(LabeledError::new("empty input"))?,
                        span,
                        call.has_flag("files")?,
                    )
                    .map_err(|e| LabeledError::new(e.to_string()))?;
                Ok(PipelineData::Value(rec, pipeline_metadata))
            }
            v => {
                return Err(LabeledError::new(format!(
                    "requires binary input, got {}",
                    v.get_type()
                ))
                .with_label("Expected binary from pipeline", call.head))
            }
        }
    }
}