Files
keep/src/modes/list.rs
Andrew Phillips b4046e0b18 refactor: Consolidate cell alignment logic in modes::list
Co-authored-by: aider (openai/andrew/openrouter/deepseek/deepseek-chat-v3.1) <aider@aider.chat>
2025-09-08 18:27:45 -03:00

214 lines
7.2 KiB
Rust

use crate::config;
use crate::services::item_service::ItemService;
use crate::services::types::ItemWithMeta;
use crate::modes::common::ColumnType;
use crate::modes::common::{size_column, string_column, OutputFormat};
use anyhow::{Result};
use log::debug;
use comfy_table::{Table, ContentArrangement, Cell, Row, Color, Attribute};
use comfy_table::presets::NOTHING;
use comfy_table::CellAlignment;
use serde::{Deserialize, Serialize};
use serde_json;
use serde_yaml;
use std::io::{stdout, IsTerminal};
#[derive(Serialize, Deserialize)]
struct ListItem {
id: Option<i64>,
time: String,
size: Option<u64>,
size_formatted: String,
compression: String,
file_size: Option<u64>,
file_size_formatted: String,
file_path: String,
tags: Vec<String>,
meta: std::collections::HashMap<String, String>,
}
pub fn mode_list(
cmd: &mut clap::Command,
settings: &config::Settings,
ids: &mut Vec<i64>,
tags: &Vec<String>,
conn: &mut rusqlite::Connection,
data_path: std::path::PathBuf,
) -> Result<()> {
if !ids.is_empty() {
cmd.error(
clap::error::ErrorKind::InvalidValue,
"ID given, you can only supply tags when using --list",
)
.exit();
}
let item_service = ItemService::new(data_path.clone());
let items_with_meta = item_service.list_items(conn, tags, &std::collections::HashMap::new())?;
let output_format = crate::modes::common::settings_output_format(settings);
if output_format != OutputFormat::Table {
return show_list_structured(items_with_meta, data_path, settings, output_format);
}
let mut table = Table::new();
table
.load_preset(NOTHING)
.set_content_arrangement(ContentArrangement::Dynamic);
if !stdout().is_terminal() {
table.force_no_tty();
}
// Create header row
let mut header_cells = Vec::new();
for column in &settings.list_format {
header_cells.push(Cell::new(&column.label).add_attribute(Attribute::Bold));
}
table.set_header(header_cells);
for item_with_meta in items_with_meta {
let tags: Vec<String> = item_with_meta.tags.iter().map(|t| t.name.clone()).collect();
let meta = item_with_meta.meta_as_map();
let item = item_with_meta.item;
let mut item_path = data_path.clone();
item_path.push(item.id.unwrap().to_string());
let mut table_row = Row::new();
for column in &settings.list_format {
let column_type = ColumnType::from_str(&column.name)
.unwrap_or_else(|_| panic!("Unknown column {:?}", column.name));
let mut meta_name: Option<&str> = None;
if let ColumnType::Meta = column_type {
let parts: Vec<&str> = column.name.split(':').collect();
if parts.len() > 1 {
meta_name = Some(parts[1]);
}
}
let mut cell = match column_type {
ColumnType::Id => Cell::new(item.id.unwrap_or(0).to_string()),
ColumnType::Time => Cell::new(
item.ts
.with_timezone(&chrono::Local)
.format("%F %T")
.to_string(),
),
ColumnType::Size => match item.size {
Some(size) => Cell::new(format_size(
size as u64,
settings.human_readable,
)),
None => match item_path.metadata() {
Ok(_) => Cell::new("Unknown")
.fg(Color::Yellow)
.add_attribute(Attribute::Bold),
Err(_) => Cell::new("Missing")
.fg(Color::Red)
.add_attribute(Attribute::Bold),
},
},
ColumnType::Compression => Cell::new(item.compression.to_string()),
ColumnType::FileSize => match item_path.metadata() {
Ok(metadata) => Cell::new(format_size(
metadata.len(),
settings.human_readable,
)),
Err(_) => Cell::new("Missing")
.fg(Color::Red)
.add_attribute(Attribute::Bold),
},
ColumnType::FilePath => Cell::new(
item_path.clone().into_os_string().into_string().unwrap(),
),
ColumnType::Tags => Cell::new(tags.join(" ")),
ColumnType::Meta => match meta_name {
Some(meta_name) => match meta.get(meta_name) {
Some(meta_value) => Cell::new(meta_value.to_string()),
None => Cell::new(""),
},
None => Cell::new(""),
},
};
// Apply alignment in one place
cell = match column.align {
crate::config::ColumnAlignment::Right => cell.set_alignment(CellAlignment::Right),
crate::config::ColumnAlignment::Left => cell.set_alignment(CellAlignment::Left),
};
table_row.add_cell(cell);
}
table.add_row(table_row);
}
println!("{}", table);
Ok(())
}
fn show_list_structured(
items_with_meta: Vec<ItemWithMeta>,
data_path: std::path::PathBuf,
settings: &config::Settings,
output_format: OutputFormat,
) -> Result<()> {
let mut list_items = Vec::new();
for item_with_meta in items_with_meta {
let tags: Vec<String> = item_with_meta.tags.iter().map(|t| t.name.clone()).collect();
let meta = item_with_meta.meta_as_map();
let item = item_with_meta.item;
let item_id = item.id.unwrap();
let mut item_path = data_path.clone();
item_path.push(item_id.to_string());
let file_size = item_path.metadata().map(|m| m.len()).ok();
let file_size_formatted = match file_size {
Some(size) => crate::modes::common::format_size(size, settings.human_readable),
None => "Missing".to_string(),
};
let size_formatted = match item.size {
Some(size) => crate::modes::common::format_size(size as u64, settings.human_readable),
None => "Unknown".to_string(),
};
let list_item = ListItem {
id: item.id,
time: item
.ts
.with_timezone(&chrono::Local)
.format("%F %T")
.to_string(),
size: item.size.map(|s| s as u64),
size_formatted,
compression: item.compression,
file_size,
file_size_formatted,
file_path: item_path.into_os_string().into_string().unwrap_or_default(),
tags,
meta,
};
list_items.push(list_item);
}
match output_format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&list_items)?);
}
OutputFormat::Yaml => {
println!("{}", serde_yaml::to_string(&list_items)?);
}
OutputFormat::Table => unreachable!(),
}
Ok(())
}