fundaia 0.32.1

Command line for the Fundaia deployment platform: projects, services, variables, deployments, logs and metrics
//! What each version brought, and what this machine has been running.
//!
//! The one command in the tool that talks to nothing: the changelog travels
//! inside the binary and the history is a file beside the profiles, so this
//! answers with no instance, no token and no network. That is deliberate —
//! "what changed?" is asked most often right after the tool has replaced itself
//! mid-command, which is not a moment to require credentials.

use anyhow::{bail, Result};
use owo_colors::OwoColorize;

use crate::changelog::{self, history, Release};
use crate::output::Format;
use crate::render::markdown;
use crate::render::theme;
use crate::render::time::relative_since;

/// Which entries were asked for.
///
/// A struct rather than four arguments, because they arrive together from the
/// parser and every one of them is optional in the same way.
pub struct Query {
    pub all: bool,
    pub version: Option<String>,
    pub since: Option<String>,
    pub count: Option<usize>,
}

/// The changelog, however much of it was asked for.
pub fn show(query: &Query, format: Format) -> Result<()> {
    let releases = changelog::releases();
    let current = env!("CARGO_PKG_VERSION");

    let chosen = match (&query.version, &query.since, query.all) {
        (Some(version), _, _) => match changelog::only(releases, version) {
            Some(release) => vec![release],
            None => bail!("no hay notas de la versión {version} en este binario"),
        },
        (_, Some(floor), _) => changelog::since(releases, floor),
        (_, _, true) => releases,
        // The version somebody is running, which is the question they have.
        _ => changelog::running(releases, current).into_iter().collect(),
    };

    let chosen = match query.count {
        Some(count) => chosen.into_iter().take(count).collect(),
        None => chosen,
    };

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

    if chosen.is_empty() {
        println!();
        println!("  {}", "No hay nada nuevo escrito.".style(theme::muted()));
        println!();
        return Ok(());
    }

    print(&chosen, current);
    Ok(())
}

/// Which versions have run on this machine, and since when.
pub fn show_history(format: Format) -> Result<()> {
    let seen = history::seen();

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

    println!();

    if seen.is_empty() {
        println!(
            "  {}",
            "Todavía no hay historial: se escribe la primera vez que cambia la versión."
                .style(theme::muted())
        );
        println!();
        return Ok(());
    }

    let current = env!("CARGO_PKG_VERSION");

    for entry in &seen {
        let when = if entry.at == 0 {
            "sin fecha".to_string()
        } else {
            // The log keeps seconds and the renderer speaks milliseconds.
            relative_since(entry.at as i64 * 1_000)
        };

        let mark = if entry.version == current {
            format!(" {}", "· ahora".style(theme::success()))
        } else {
            String::new()
        };

        println!(
            "  {}  {}{}",
            entry.version.style(theme::strong()),
            when.style(theme::muted()),
            mark
        );
    }

    println!();
    Ok(())
}

fn print(releases: &[Release], current: &str) {
    for release in releases {
        println!();

        let date = match &release.date {
            Some(date) => format!("  {date}"),
            None => String::new(),
        };

        let mark = if release.version == current {
            format!("  {}", "· la que estás usando".style(theme::success()))
        } else {
            String::new()
        };

        println!(
            "  {}{}{}",
            format!("v{}", release.version).style(theme::accent()),
            date.style(theme::muted()),
            mark
        );
        println!();
        println!("{}", markdown::body(&release.body));
    }

    println!();
}