ao-cli 0.1.6

A unified administration tool for Linux systems
use super::common::SystemCommand;
use crate::cli::{DistroAction, DistroArgs};
use crate::os::{DistroInfo, DistroManager, Domain, ExecutableCommand, OutputFormat};
use anyhow::Result;
use clap::{ArgMatches, Args, Command as ClapCommand, FromArgMatches};

pub struct StandardDistro;

impl Domain for StandardDistro {
    fn name(&self) -> &'static str {
        "distro"
    }
    fn command(&self) -> ClapCommand {
        DistroArgs::augment_args(ClapCommand::new("distro").about("Manage distributions"))
    }
    fn execute(
        &self,
        matches: &ArgMatches,
        _app: &ClapCommand,
    ) -> Result<Box<dyn ExecutableCommand>> {
        let args = DistroArgs::from_arg_matches(matches)?;
        match &args.action {
            Some(DistroAction::Info { format }) => self.info(*format),
            Some(DistroAction::Upgrade) => self.upgrade(),
            None => self.info(OutputFormat::Table),
        }
    }
}

impl DistroManager for StandardDistro {
    fn info(&self, format: OutputFormat) -> Result<Box<dyn ExecutableCommand>> {
        Ok(Box::new(DistroInfoCommand { format }))
    }

    fn upgrade(&self) -> Result<Box<dyn ExecutableCommand>> {
        if std::path::Path::new("/usr/bin/do-release-upgrade").exists() {
            Ok(Box::new(SystemCommand::new("do-release-upgrade")))
        } else if std::path::Path::new("/usr/bin/dnf").exists() {
            Ok(Box::new(
                SystemCommand::new("dnf")
                    .arg("system-upgrade")
                    .arg("reboot"),
            ))
        } else {
            anyhow::bail!("Distribution upgrade tool not found")
        }
    }
}

pub struct DistroInfoCommand {
    pub format: OutputFormat,
}
impl ExecutableCommand for DistroInfoCommand {
    fn execute(&self) -> Result<()> {
        if matches!(self.format, OutputFormat::Original) {
            return SystemCommand::new("cat").arg("/etc/os-release").execute();
        }

        let mut info = DistroInfo {
            name: String::new(),
            version: String::new(),
            id: String::new(),
            id_like: String::new(),
            pretty_name: String::new(),
        };
        if let Ok(content) = std::fs::read_to_string("/etc/os-release") {
            for line in content.lines() {
                let parts: Vec<&str> = line.split('=').collect();
                if parts.len() == 2 {
                    let val = parts[1].trim_matches('"').to_string();
                    match parts[0] {
                        "NAME" => info.name = val,
                        "VERSION" => info.version = val,
                        "ID" => info.id = val,
                        "ID_LIKE" => info.id_like = val,
                        "PRETTY_NAME" => info.pretty_name = val,
                        _ => {}
                    }
                }
            }
        }

        match self.format {
            OutputFormat::Table => {
                let mut table = comfy_table::Table::new();
                table.set_header(vec!["Property", "Value"]);
                table.add_row(vec!["Name", &info.name]);
                table.add_row(vec!["Version", &info.version]);
                table.add_row(vec!["ID", &info.id]);
                table.add_row(vec!["ID_LIKE", &info.id_like]);
                table.add_row(vec!["Pretty Name", &info.pretty_name]);
                println!("{}", table);
            }
            OutputFormat::Json => {
                println!("{}", serde_json::to_string_pretty(&info)?);
            }
            OutputFormat::Yaml => {
                println!("{}", serde_yaml::to_string(&info)?);
            }
            OutputFormat::Original => unreachable!(),
        }
        Ok(())
    }
    fn as_string(&self) -> String {
        "cat /etc/os-release".to_string()
    }
    fn is_structured(&self) -> bool {
        matches!(
            self.format,
            OutputFormat::Json | OutputFormat::Yaml | OutputFormat::Original
        )
    }
}