fundaia 0.53.0

Command line for the Fundaia deployment platform: projects, services, variables, deployments, logs and metrics
use anyhow::Result;
use owo_colors::OwoColorize;

use crate::api::client::ApiClient;
use crate::output::{self, Format};
use crate::render::sanitise;
use crate::render::table::{Cell, Table};
use crate::render::theme;
use crate::render::time;

pub async fn list(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    path: Option<&str>,
    format: Format,
) -> Result<()> {
    let (_, service) = client.locate(project_reference, service_reference).await?;
    let answer = client.list_files(&service.id, path).await?;

    if format.answered_in_json(&answer.raw) {
        return Ok(());
    }

    let listing = &answer.body;
    println!(
        "  {}",
        sanitise::plain(&listing.path).style(theme::strong())
    );
    let mut table = Table::new(&["nombre", "tamaño", "modificado"]);
    for entry in &listing.entries {
        let directory = entry.kind == "directory";
        table.push(vec![
            Cell::styled(
                if directory {
                    format!("{}/", sanitise::plain(&entry.name))
                } else {
                    sanitise::plain(&entry.name)
                },
                if directory {
                    theme::accent()
                } else {
                    theme::strong()
                },
            ),
            Cell::plain(if directory {
                "—".to_owned()
            } else {
                size_word(entry.size_bytes)
            }),
            Cell::styled(
                time::relative_time(&entry.modified_at).unwrap_or_default(),
                theme::muted(),
            ),
        ]);
    }
    table.print_or("Esta carpeta está vacía.");
    Ok(())
}

pub async fn cat(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    path: &str,
    format: Format,
) -> Result<()> {
    let (_, service) = client.locate(project_reference, service_reference).await?;
    let answer = client.preview_file(&service.id, path).await?;

    if format.answered_in_json(&answer.raw) {
        return Ok(());
    }

    let preview = &answer.body;
    match &preview.text {
        Some(text) => {
            print!("{}", sanitise::with_colour(text));
            if !text.ends_with('\n') {
                println!();
            }
            if preview.truncated {
                eprintln!(
                    "  {}",
                    format!(
                        "Solo los primeros 256 KB de {}: `fundaia files get` lo descarga entero.",
                        size_word(preview.size_bytes)
                    )
                    .style(theme::warning())
                );
            }
        }
        None => eprintln!(
            "  {}",
            format!(
                "Es un archivo binario de {}: `fundaia files get` lo descarga.",
                size_word(preview.size_bytes)
            )
            .style(theme::muted())
        ),
    }
    Ok(())
}

pub async fn get(
    client: &ApiClient,
    project_reference: &str,
    service_reference: &str,
    path: &str,
    into: Option<&str>,
    format: Format,
) -> Result<()> {
    let (_, service) = client.locate(project_reference, service_reference).await?;
    let name = path
        .rsplit('/')
        .next()
        .filter(|name| !name.is_empty())
        .unwrap_or("archivo");
    let target = into.unwrap_or(name).to_owned();
    let bytes = client
        .download_file(&service.id, path, std::path::Path::new(&target))
        .await?;

    if format.is_json() {
        output::emit(&serde_json::json!({ "path": path, "file": target, "bytes": bytes }));
        return Ok(());
    }
    println!(
        "  {} {} {}",
        theme::CHECK.style(theme::success()),
        target.style(theme::strong()),
        format!("({})", size_word(bytes)).style(theme::muted())
    );
    Ok(())
}

fn size_word(bytes: u64) -> String {
    const KB: f64 = 1024.0;
    let size = bytes as f64;
    if size < KB {
        format!("{bytes} B")
    } else if size < KB * KB {
        format!("{:.1} KB", size / KB)
    } else if size < KB * KB * KB {
        format!("{:.1} MB", size / KB / KB)
    } else {
        format!("{:.1} GB", size / KB / KB / KB)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_should_say_a_small_file_in_bytes() {
        assert_eq!(size_word(512), "512 B");
    }

    #[test]
    fn it_should_say_a_large_file_in_megabytes() {
        assert_eq!(size_word(5 * 1024 * 1024), "5.0 MB");
    }
}