From f0b43360d8e7c293085065071617bf3b5b94d553 Mon Sep 17 00:00:00 2001 From: Wynd Date: Mon, 5 May 2025 01:42:48 +0300 Subject: [PATCH] Added an arg for listing untagged files and added amount of files tagged with certain tags --- src/cli.rs | 3 +++ src/tags.rs | 67 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index aea511a..be95100 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -51,4 +51,7 @@ pub struct UntagArgs { #[derive(Debug, Args, PartialEq, Eq)] pub struct FilesArgs { pub query: Option, + + #[arg(long, short, exclusive = true, default_value_t = false)] + pub all: bool, } diff --git a/src/tags.rs b/src/tags.rs index ffb6b7e..c15a665 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,12 +1,16 @@ use std::{ + fs, io::{self, Write}, - path::PathBuf, + path::{self, PathBuf}, str::FromStr, }; use anyhow::{Result, anyhow}; -use rusqlite::{Connection, params, params_from_iter, types::Value}; +use rusqlite::{Connection, params_from_iter, types::Value}; + +pub const TERM_RESET: &str = "\x1b[0m"; +pub const TERM_BOLD: &str = "\x1b[1m"; use crate::{ cli::{self, FilesArgs, TagArgs, TagsArgs, UntagArgs}, @@ -23,11 +27,19 @@ pub fn handle_files(args: FilesArgs) -> Result<()> { let mut show_tags = true; - if let Some(query) = args.query { + let mut paths: Vec = vec![]; + if args.all { + paths = fs::read_dir(".")? + .filter_map(|f| f.ok()) + .filter(|f| f.metadata().is_ok() && f.metadata().unwrap().is_file()) + .map(|f| f.path()) + .collect(); + } else if let Some(query) = args.query { let path = PathBuf::from_str(&query).unwrap_or_default(); match path.exists() { true => { sql.push_str(&format!(" WHERE path IN ('{}')", query)); + paths = vec![path]; } false => { sql.push_str(&format!(" WHERE tag.name LIKE '%{}%'", query)); @@ -36,17 +48,30 @@ pub fn handle_files(args: FilesArgs) -> Result<()> { }; } + let mut all_paths: Vec = paths + .iter_mut() + .map(|f| f.to_string_lossy().replace("./", "").to_string()) + .collect(); + sql.push_str(" GROUP BY path"); let mut stmt = conn.prepare(&sql)?; - let result: Vec<(String, String)> = stmt + let tagged_files: Vec<(String, String)> = stmt .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))? .flatten() .collect(); let mut w = io::stdout(); - if !result.is_empty() { - for (file, tags) in result { + if !tagged_files.is_empty() { + if args.all { + writeln!(&mut w, "{TERM_BOLD}Tagged Files: {TERM_RESET}")?; + } + for (file, tags) in tagged_files { + // Remove already tagged files + if let Some(idx) = all_paths.iter().position(|v| *v == file) { + all_paths.swap_remove(idx); + } + if show_tags { writeln!(&mut w, "{}: {}", file, tags)?; } else { @@ -56,6 +81,15 @@ pub fn handle_files(args: FilesArgs) -> Result<()> { } else { writeln!(&mut w, "No files registered!")?; } + + if args.all && !all_paths.is_empty() { + writeln!(&mut w, "\n{TERM_BOLD}Untagged Files: {TERM_RESET}")?; + for path in all_paths { + let path_str = path; + writeln!(&mut w, "{}", path_str)?; + } + } + w.flush()?; Ok(()) @@ -86,7 +120,7 @@ pub fn handle_tags(args: TagsArgs) -> Result<()> { let tags = list_tags(&conn)?; if !tags.is_empty() { for tag in tags { - writeln!(&mut w, "{}", tag)?; + writeln!(&mut w, "{} - {}", tag.0, tag.1)?; } } else { writeln!(&mut w, "No tags registered!")?; @@ -108,13 +142,20 @@ fn rename_tag(conn: &Connection, old: String, new: String) -> Result<()> { Ok(()) } -fn list_tags(conn: &Connection) -> Result> { - let mut stmt = conn.prepare("SELECT name FROM tag")?; - let result = stmt.query_map([], |row| row.get(0))?.flatten(); +fn list_tags(conn: &Connection) -> Result> { + let mut stmt = conn.prepare( + r#"SELECT name, COUNT() as count FROM tag + INNER JOIN file_tag ON file_tag.tag_id = tag.id + GROUP BY name + ORDER BY count DESC"#, + )?; + let result = stmt + .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))? + .flatten(); - let mut tags = Vec::new(); - for name in result { - tags.push(name); + let mut tags: Vec<(String, i32)> = Vec::new(); + for entry in result { + tags.push(entry); } Ok(tags)