ordinary 0.6.0-pre.14

Ordinary CLI
Documentation
// Copyright (C) 2026 Ordinary Labs, LLC.
//
// SPDX-License-Identifier: AGPL-3.0-only

use clap::Subcommand;
use comfy_table::Table;
use ordinary_monitor::LogFileMetadata;
use std::collections::BTreeMap;

#[derive(Clone, Debug)]
pub enum LogFormat {
    /// all logs that match query
    All,
    /// top logs that match query
    Top,
    /// count of logs that match query
    Count,
}

impl LogFormat {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::All => "all",
            Self::Top => "top",
            Self::Count => "count",
        }
    }
}

impl clap::ValueEnum for LogFormat {
    fn value_variants<'a>() -> &'a [Self] {
        &[Self::All, Self::Top, Self::Count]
    }

    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
        match self {
            Self::All => Some(clap::builder::PossibleValue::new("all")),
            Self::Top => Some(clap::builder::PossibleValue::new("top")),
            Self::Count => Some(clap::builder::PossibleValue::new("count")),
        }
    }
}

#[derive(Subcommand, Debug)]
pub enum Logs {
    /// sync files, indexes and tables
    Sync {
        #[command(subcommand)]
        sync: Sync,
    },
    /// search the tantivy index
    Search {
        /// format
        format: LogFormat,

        /// [reference](https://quickwit.io/docs/reference/query-language)
        query: String,

        #[arg(long)]
        /// limit (when using 'top' format)
        limit: Option<usize>,

        #[arg(short, long)]
        /// whether to sync from remote
        sync: Option<bool>,
    },
}

#[derive(Subcommand, Debug)]
pub enum Sync {
    /// get information about remote and local log files.
    ///
    /// - "✅" remote and local are synced
    /// - "❌" not found on remote *or* local
    /// - "⚠️" present on both but local is out of sync
    Info,

    /// sync a single file.
    File {
        /// file name
        name: String,
    },

    /// repair local state for all out of sync (⚠️) and remote
    /// files not yet downloaded (❌).
    All {
        #[arg(short, long)]
        /// download all files from the server,
        /// overwriting everything you have locally.
        ///
        /// Note: will preserve the state of any files the server
        /// no longer has a record of.
        force: Option<bool>,
    },
}

pub fn print_logs_metadata_table(
    remote_metadata_vec: Vec<LogFileMetadata>,
    local_metadata_vec: Vec<LogFileMetadata>,
) {
    let mut table = Table::new();
    table.set_header(vec!["file", "size", "remote", "local"]);

    let mut rows_map = BTreeMap::new();

    for metadata in remote_metadata_vec {
        rows_map.insert(
            metadata.name.clone(),
            vec![
                metadata.name.clone(),
                format!("{}", bytesize::ByteSize(metadata.size).display().si_short()),
                "".to_string(),
                "".to_string(),
            ],
        );
    }

    for metadata in local_metadata_vec {
        if let Some(row) = rows_map.get_mut(&metadata.name) {
            let local_size = format!("{}", bytesize::ByteSize(metadata.size).display().si_short());

            if row[1] == local_size {
                row[3] = "".to_string();
            } else {
                row[3] = "⚠️".to_string();
            }

            row[1] = format!("{local_size}/{}", row[1]);
        } else {
            rows_map.insert(
                metadata.name.clone(),
                vec![
                    metadata.name.clone(),
                    format!("{}", bytesize::ByteSize(metadata.size).display().si_short()),
                    "".to_string(),
                    "".to_string(),
                ],
            );
        }
    }

    for (_, row) in rows_map {
        table.add_row(row);
    }

    println!("\n{table}\n");
}