Added tag command for tagging specific files
parent
131bdc8b1f
commit
45eb3fcbbd
20
src/cli.rs
20
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<String>,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub commands: Option<TagsCommands>,
|
||||
|
@ -24,6 +27,19 @@ pub struct TagsArgs {
|
|||
|
||||
#[derive(Debug, Subcommand, PartialEq, Eq)]
|
||||
pub enum TagsCommands {
|
||||
List,
|
||||
Add { add: Vec<String> },
|
||||
Remove { remove: Vec<String> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Args, PartialEq, Eq)]
|
||||
pub struct TagArgs {
|
||||
pub file: PathBuf,
|
||||
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args, PartialEq, Eq)]
|
||||
pub struct FilesArgs {
|
||||
pub query: Option<String>,
|
||||
}
|
||||
|
|
77
src/main.rs
77
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<String> {
|
||||
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<String>) {
|
||||
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<String>) {
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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<String> {
|
||||
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<String>) {
|
||||
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<String>) {
|
||||
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<String>) {
|
||||
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<i32, rusqlite::Error> { 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<i32, rusqlite::Error> { 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");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue