From 45eb3fcbbd7979c521f0352a449be5af482382c7 Mon Sep 17 00:00:00 2001 From: Wynd Date: Sat, 3 May 2025 15:51:11 +0300 Subject: [PATCH] Added tag command for tagging specific files --- src/cli.rs | 20 +++++++- src/main.rs | 77 +++++------------------------ src/tags.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 66 deletions(-) create mode 100644 src/tags.rs diff --git a/src/cli.rs b/src/cli.rs index 1360028..6eeb9f9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use clap::{Args, Parser, Subcommand}; #[derive(Debug, Parser, PartialEq, Eq)] @@ -10,13 +12,14 @@ pub struct CliArgs { #[derive(Debug, Subcommand, PartialEq, Eq)] pub enum Commands { Init, + Tag(TagArgs), Tags(TagsArgs), + Files(FilesArgs), } #[derive(Debug, Args, PartialEq, Eq)] pub struct TagsArgs { - #[arg(long("list"), short)] - pub list: bool, + pub query: Option, #[command(subcommand)] pub commands: Option, @@ -24,6 +27,19 @@ pub struct TagsArgs { #[derive(Debug, Subcommand, PartialEq, Eq)] pub enum TagsCommands { + List, Add { add: Vec }, Remove { remove: Vec }, } + +#[derive(Debug, Args, PartialEq, Eq)] +pub struct TagArgs { + pub file: PathBuf, + + pub tags: Vec, +} + +#[derive(Debug, Args, PartialEq, Eq)] +pub struct FilesArgs { + pub query: Option, +} diff --git a/src/main.rs b/src/main.rs index 40886e7..18695f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,13 @@ -use std::{io, path, process::exit}; - -use io::Write; +use std::{path, process::exit}; use clap::Parser; use cli::CliArgs; -use rusqlite::{Connection, params_from_iter}; +use rusqlite::Connection; const DB_PATH: &str = ".tags"; mod cli; +mod tags; fn main() { let args = CliArgs::parse(); @@ -23,68 +22,12 @@ fn main() { } exit(0); } - cli::Commands::Tags(args) => { - let conn = Connection::open(DB_PATH).unwrap(); - - if args.list { - let mut w = io::stdout(); - let tags = list_tags(&conn); - for tag in tags { - writeln!(&mut w, "{}", tag).unwrap(); - } - w.flush().unwrap(); - return; - } - - match args.commands { - Some(cli::TagsCommands::Add { add }) => add_tags(&conn, add), - Some(cli::TagsCommands::Remove { remove }) => remove_tags(&conn, remove), - _ => (), - }; - } + cli::Commands::Tag(args) => tags::handle_tag(args), + cli::Commands::Tags(args) => tags::handle_tags(args), + cli::Commands::Files(args) => tags::handle_files(args), } } -fn list_tags(conn: &Connection) -> Vec { - let mut stmt = conn.prepare("SELECT name FROM tag").unwrap(); - let result = stmt.query_map([], |row| row.get(0)).unwrap(); - - let mut tags = Vec::new(); - for name in result { - tags.push(name.unwrap()); - } - - tags -} - -fn remove_tags(conn: &Connection, tags: Vec) { - let mut query = r#"DELETE FROM tag WHERE name IN ("#.to_string(); - - for (i, _tag) in tags.iter().enumerate() { - query.push('?'); - if i < tags.len() - 1 { - query.push(','); - } - } - - query.push(')'); - - conn.execute(&query, params_from_iter(tags)).unwrap(); -} - -fn add_tags(conn: &Connection, tags: Vec) { - let mut query = r#"INSERT INTO tag(name) VALUES"#.to_string(); - - for (i, _tag) in tags.iter().enumerate() { - query.push_str("(?)"); - if i < tags.len() - 1 { - query.push(','); - } - } - - conn.execute(&query, params_from_iter(tags)).unwrap(); -} - fn init_db(conn: &Connection) { conn.execute( r#"CREATE TABLE IF NOT EXISTS tag( @@ -97,7 +40,7 @@ fn init_db(conn: &Connection) { conn.execute( r#"CREATE TABLE IF NOT EXISTS file( - id INT NOT NULL PRIMARY KEY, + id INTEGER PRIMARY KEY AUTOINCREMENT, path VARCHAR(255) NOT NULL UNIQUE );"#, (), @@ -114,6 +57,12 @@ fn init_db(conn: &Connection) { .unwrap(); } +pub fn try_database() { + if !has_database() { + panic!("No database found! Use taggo init to create one first."); + } +} + pub fn has_database() -> bool { path::Path::new(DB_PATH).exists() } diff --git a/src/tags.rs b/src/tags.rs new file mode 100644 index 0000000..3ab62fe --- /dev/null +++ b/src/tags.rs @@ -0,0 +1,140 @@ +use std::{ + io::{self, Write}, + path::PathBuf, + str::FromStr, +}; + +use rusqlite::{Connection, params_from_iter}; + +use crate::{ + DB_PATH, + cli::{self, FilesArgs, TagArgs, TagsArgs}, + try_database, +}; + +pub fn handle_tag(args: TagArgs) { + try_database(); + + let mut conn = Connection::open(DB_PATH).unwrap(); + + tag_file(&mut conn, args.file, args.tags); +} + +pub fn handle_tags(args: TagsArgs) { + try_database(); + + let conn = Connection::open(DB_PATH).unwrap(); + + match args.commands { + Some(cli::TagsCommands::List) | None => { + let mut w = io::stdout(); + let tags = list_tags(&conn); + for tag in tags { + writeln!(&mut w, "{}", tag).unwrap(); + } + w.flush().unwrap(); + } + Some(cli::TagsCommands::Add { add }) => add_tags(&conn, add), + Some(cli::TagsCommands::Remove { remove }) => remove_tags(&conn, remove), + }; +} + +fn list_tags(conn: &Connection) -> Vec { + let mut stmt = conn.prepare("SELECT name FROM tag").unwrap(); + let result = stmt.query_map([], |row| row.get(0)).unwrap(); + + let mut tags = Vec::new(); + for name in result { + tags.push(name.unwrap()); + } + + tags +} + +fn remove_tags(conn: &Connection, tags: Vec) { + let mut query = r#"DELETE FROM tag WHERE name IN ("#.to_string(); + + for (i, _tag) in tags.iter().enumerate() { + query.push('?'); + if i < tags.len() - 1 { + query.push(','); + } + } + + query.push(')'); + + conn.execute(&query, params_from_iter(tags)).unwrap(); +} + +fn add_tags(conn: &Connection, tags: Vec) { + let mut query = r#"INSERT INTO tag(name) VALUES"#.to_string(); + + for (i, _tag) in tags.iter().enumerate() { + query.push_str("(?)"); + if i < tags.len() - 1 { + query.push(','); + } + } + + conn.execute(&query, params_from_iter(tags)).unwrap(); +} + +fn tag_file(conn: &mut Connection, file: PathBuf, tags: Vec) { + let file = file.to_str().unwrap().to_string(); + + let tx = conn.transaction().unwrap(); + { + let mut stmt = tx + .prepare(r#"INSERT INTO file(path) VALUES (?) RETURNING id"#) + .unwrap(); + let file_id = stmt + .query_row([file], |row| -> Result { row.get(0) }) + .unwrap(); + + let mut sql = "SELECT id FROM tag WHERE name IN ".to_string(); + sql.push('('); + let size = tags.len(); + for (i, _tag) in tags.iter().enumerate() { + sql.push('?'); + if i < size - 1 { + sql.push(','); + } + } + sql.push(')'); + + let mut stmt = tx.prepare(&sql).unwrap(); + let result = stmt + .query_map( + params_from_iter(tags), + |row| -> Result { row.get(0) }, + ) + .unwrap(); + + let mut tags_ids = Vec::new(); + for id in result.flatten() { + tags_ids.push(id); + } + + let mut stmt = tx + .prepare(r#"INSERT INTO file_tag(tag_id, file_id) VALUES (?, ?)"#) + .unwrap(); + + for tag_id in tags_ids { + stmt.execute((file_id, tag_id)).unwrap(); + } + } + tx.commit().unwrap(); +} + +pub fn handle_files(args: FilesArgs) { + if let Some(query) = args.query { + // match PathBuf::from_str(&query) { + // Ok(path) => { + // dbg!(path); + // } + // Err(_) => { + // dbg!("a query"); + // } + // } + } +}