From 1628e0889ef83ac2380bc9e51bf0b7595ceeabd1 Mon Sep 17 00:00:00 2001 From: "Andrew Phillips (aider)" Date: Sat, 10 May 2025 12:55:30 -0300 Subject: [PATCH] refactor: move mode_list to src/modes/list.rs and update imports --- src/main.rs | 3 +- src/modes/list.rs | 218 ++++++++++++++++++++++++++++++++++++++++++++++ src/modes/mod.rs | 1 + 3 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 src/modes/list.rs diff --git a/src/main.rs b/src/main.rs index 0dc9a68..6deedb2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,7 @@ pub mod db; //pub mod item; use compression::CompressionType; +use modes::list::mode_list; extern crate term; @@ -309,7 +310,7 @@ fn main() -> Result<(), Error> { crate::modes::get::mode_get(&mut cmd, args, ids, tags, &mut conn, data_path)? } KeepModes::Diff => mode_diff(&mut cmd, args, ids, tags, &mut conn, data_path)?, - KeepModes::List => mode_list(&mut cmd, args, ids, tags, &mut conn, data_path)?, + KeepModes::List => mode_list::mode_list(&mut cmd, args, ids, tags, &mut conn, data_path)?, KeepModes::Update => { crate::modes::update::mode_update(&mut cmd, args, ids, tags, &mut conn, data_path)? } diff --git a/src/modes/list.rs b/src/modes/list.rs new file mode 100644 index 0000000..2e1751f --- /dev/null +++ b/src/modes/list.rs @@ -0,0 +1,218 @@ +use anyhow::{anyhow, Error, Result}; +use clap::Command; +use std::collections::HashMap; +use std::path::PathBuf; +use std::str::FromStr; + +use crate::compression::CompressionType; +use crate::db::{get_item, get_item_last, get_items, get_items_matching}; +use crate::modes::common::{format_size, get_format_box_chars_no_border_line_separator}; +use crate::modes::common::{string_column, size_column}; +use crate::modes::common::ColumnType; +use is_terminal::IsTerminal; +use prettytable::format::TableFormat; +use prettytable::row; +use prettytable::{Attr, Cell, Row, Table}; + +pub fn mode_list( + cmd: &mut Command, + args: crate::Args, + ids: &mut Vec, + tags: &Vec, + conn: &mut Connection, + data_path: PathBuf, +) -> Result<()> { + if !ids.is_empty() { + cmd.error( + ErrorKind::InvalidValue, + "ID given, you can only supply tags when using --list", + ) + .exit(); + } + + let mut meta: HashMap = HashMap::new(); + for item in args.item.meta.iter() { + let item = item.clone(); + meta.insert(item.key, item.value); + } + + let items = match tags.is_empty() && meta.is_empty() { + true => get_items(conn)?, + false => get_items_matching(conn, tags, &meta)?, + }; + + debug!("MAIN: Items: {:?}", items); + + let mut tags_by_item: HashMap> = HashMap::new(); + let mut meta_by_item: HashMap> = HashMap::new(); + + for item in items.iter() { + let item_id = item.id.unwrap(); + + let item_tags: Vec = get_item_tags(conn, item)? + .into_iter() + .map(|x| x.name) + .collect(); + tags_by_item.insert(item_id, item_tags); + + let mut item_meta: HashMap = HashMap::new(); + + for meta in get_item_meta(conn, item)? { + item_meta.insert(meta.name.clone(), meta.value); + } + meta_by_item.insert(item_id, item_meta); + } + + let mut table = Table::new(); + table.set_format(*format::consts::FORMAT_CLEAN); + + let list_format = args.options.list_format.split(","); + + let mut title_row = row!(); + + for column in list_format.clone() { + let mut column_format = column.split(":").into_iter(); + let column_name = column_format.next().expect("Unable to parse column name"); + let column_type = ColumnType::from_str(column_name) + .expect(format!("Unknown column {:?}", column_name).as_str()); + + if column_type == ColumnType::Meta { + let meta_name = column_format + .next() + .expect("Unable to parse metadata name for meta column"); + title_row.add_cell(Cell::new(meta_name).with_style(Attr::Bold)); + } else { + title_row.add_cell(Cell::new(&column_type.to_string()).with_style(Attr::Bold)); + } + } + + table.set_titles(title_row); + + for item in items { + let item_id = item.id.unwrap(); + let tags = tags_by_item.get(&item_id).unwrap(); + let meta = meta_by_item.get(&item_id).unwrap(); + let mut item_path = data_path.clone(); + item_path.push(item.id.unwrap().to_string()); + + let mut table_row = Row::new(vec![]); + + for column in list_format.clone() { + let mut column_format = column.split(":").into_iter(); + let column_name = column_format.next().expect("Unable to parse column name"); + let column_type = ColumnType::from_str(column_name) + .expect(format!("Unknown column {:?}", column_name).as_str()); + let mut meta_name: Option<&str> = None; + + if column_type == ColumnType::Meta { + meta_name = column_format.next(); + } + + let column_width: usize = match column_format.next() { + Some(len) => len.parse().unwrap_or(0), + None => 0, + }; + + let cell = match column_type { + ColumnType::Id => Cell::new_align( + &string_column(item.id.unwrap_or(0).to_string(), column_width), + Alignment::RIGHT, + ), + ColumnType::Time => Cell::new(&string_column( + item.ts.with_timezone(&Local).format("%F %T").to_string(), + column_width, + )), + ColumnType::Size => match item.size { + Some(size) => Cell::new_align( + &size_column(size as u64, args.options.human_readable, column_width), + Alignment::RIGHT, + ), + None => match item_path.metadata() { + Ok(_) => Cell::new_align("Unknown", Alignment::RIGHT) + .with_style(Attr::ForegroundColor(color::YELLOW)) + .with_style(Attr::Bold), + Err(_) => Cell::new_align("Missing", Alignment::RIGHT) + .with_style(Attr::ForegroundColor(color::RED)) + .with_style(Attr::Bold), + }, + }, + ColumnType::Compression => { + Cell::new(&string_column(item.compression.to_string(), column_width)) + } + ColumnType::FileSize => match item_path.metadata() { + Ok(metadata) => Cell::new_align( + &size_column( + metadata.len() as u64, + args.options.human_readable, + column_width, + ), + Alignment::RIGHT, + ), + Err(_) => Cell::new_align("Missing", Alignment::RIGHT) + .with_style(Attr::ForegroundColor(color::RED)) + .with_style(Attr::Bold), + }, + ColumnType::FilePath => Cell::new(&string_column( + item_path.clone().into_os_string().into_string().unwrap(), + column_width, + )), + ColumnType::Tags => Cell::new(&string_column(tags.join(" "), column_width)), + ColumnType::Meta => match meta_name { + Some(meta_name) => match meta.get(meta_name) { + Some(meta_value) => { + Cell::new(&string_column(meta_value.to_string(), column_width)) + } + None => Cell::new(""), + }, + None => Cell::new(""), + }, + }; + table_row.add_cell(cell); + } + table.add_row(table_row); + } + + table.printstd(); + + Ok(()) +} + +// These helper functions are needed for the mode_list function +pub fn get_item_tags(conn: &Connection, item: &Item) -> Result> { + debug!("DB: Getting tags for item: {:?}", item); + let mut statement = conn + .prepare_cached("SELECT id, name FROM tags WHERE id=?1 ORDER BY name ASC") + .context("Problem preparing SQL statement")?; + let mut rows = statement.query([item.id])?; + + let mut tags = Vec::new(); + + while let Some(row) = rows.next()? { + tags.push(Tag { + id: row.get(0)?, + name: row.get(1)?, + }); + } + + Ok(tags) +} + +pub fn get_item_meta(conn: &Connection, item: &Item) -> Result> { + debug!("DB: Getting item meta: {:?}", item); + let mut statement = conn + .prepare_cached("SELECT id, name, value FROM metas WHERE id=?1 ORDER BY name ASC") + .context("Problem preparing SQL statement")?; + let mut rows = statement.query([item.id])?; + + let mut metas = Vec::new(); + + while let Some(row) = rows.next()? { + metas.push(Meta { + id: row.get(0)?, + name: row.get(1)?, + value: row.get(2)?, + }); + } + + Ok(metas) +} diff --git a/src/modes/mod.rs b/src/modes/mod.rs index dcd9bbf..3367c0f 100644 --- a/src/modes/mod.rs +++ b/src/modes/mod.rs @@ -2,6 +2,7 @@ pub mod common; pub mod delete; pub mod get; pub mod info; +pub mod list; pub mod save; pub mod status; pub mod update;