commit 2fb92399eab3a9c9b99ad95d22b1f137f7895d72 Author: vanten-s Date: Sun May 5 13:35:12 2024 +0200 CLI done, moving onto the GUI diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f3c5ca1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tagger" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.5.4", features = ["derive"] } +color-eyre = "0.6.3" +config = "0.14.0" +serde = { version = "1.0.200", features = ["derive"] } +serde_json = "1.0.116" +toml = "0.8.12" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..7e705c2 --- /dev/null +++ b/shell.nix @@ -0,0 +1,14 @@ +{ pkgs ? import {} }: + +pkgs.mkShell rec { + buildInputs = with pkgs; [ + rustup + ]; + RUSTC_VERSION = "stable"; + shellHook = '' + rustup component add rust-analyzer + rustup default $RUSTC_VERSION + export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin + export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/nightly-x86_64-unknown-linux-gnu/bin/ + ''; +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..76027cb --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,47 @@ +use std::{path::PathBuf, process::exit}; + +use clap::{Parser, Subcommand}; +use color_eyre::eyre::Result; +use tracing::info; + +use crate::{commands, State}; + +#[derive(Parser, Debug, Clone, PartialEq, Eq)] +#[command(version, about, long_about = None)] +pub struct Cli { + #[arg(short, long)] + pub state: Option, + #[command(subcommand)] + pub command: Option, +} + +#[derive(Debug, Clone, Subcommand, PartialEq, Eq)] +pub enum Commands { + Print { tag: Option }, + Tag { tag: String, objects: Vec }, + Remove { tag: String, object: String }, + Init, + Index, +} + +pub fn run(flags: &Cli) -> Result<()> { + let command = flags.command.clone().unwrap(); + if command == Commands::Init { + info!("Creating empty .state.json"); + commands::init()?; + exit(0) + } + + let mut state = State::get_state(&flags)?; + + match &command { + Commands::Print { tag } => commands::print(&state, &tag), + Commands::Tag { tag, objects } => commands::tag(&mut state, tag, objects), + Commands::Remove { tag, object } => commands::remove(&mut state, tag, object)?, + Commands::Init => exit(1), + Commands::Index => commands::index(&mut state)?, + } + + state.write_state(&flags)?; + Ok(()) +} diff --git a/src/commands/index.rs b/src/commands/index.rs new file mode 100644 index 0000000..a2d0c14 --- /dev/null +++ b/src/commands/index.rs @@ -0,0 +1,27 @@ +use std::{collections::HashSet, path::PathBuf, str::FromStr}; + +use color_eyre::eyre::Result; + +use crate::State; + +pub fn index(state: &mut State) -> Result<()> { + for file in index_recursion(&state, PathBuf::from_str(".")?) { + if state.tags.get(&file).is_some() { + continue; + } + state.tags.insert(file, HashSet::new()); + } + Ok(()) +} + +fn index_recursion(state: &State, current_path: PathBuf) -> Vec { + let mut paths = Vec::new(); + let is_file = std::fs::metadata(¤t_path).unwrap().is_file(); + if is_file { + return vec![current_path.to_string_lossy().to_string()]; + } + for path in std::fs::read_dir(¤t_path).unwrap() { + paths.append(&mut index_recursion(state, path.unwrap().path())); + } + paths +} diff --git a/src/commands/init.rs b/src/commands/init.rs new file mode 100644 index 0000000..2b68e2a --- /dev/null +++ b/src/commands/init.rs @@ -0,0 +1,13 @@ +use std::collections::HashMap; + +use color_eyre::eyre::Result; + +use crate::State; + +pub fn init() -> Result<()> { + let state = State { + tags: HashMap::new(), + }; + std::fs::write(".state.json", serde_json::to_string_pretty(&state)?)?; + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..8b15e5a --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,11 @@ +pub mod index; +pub mod init; +pub mod print; +pub mod remove; +pub mod tag; + +pub use index::*; +pub use init::*; +pub use print::*; +pub use remove::*; +pub use tag::*; diff --git a/src/commands/print.rs b/src/commands/print.rs new file mode 100644 index 0000000..6567ae3 --- /dev/null +++ b/src/commands/print.rs @@ -0,0 +1,36 @@ +use std::collections::HashSet; + +use crate::State; + +pub fn print(state: &State, tag: &Option) { + match tag { + Some(tag) => print_recursion(&state, &mut HashSet::new(), &tag, 0), + None => { + for tag in { + let mut keys = state.tags.keys().collect::>(); + keys.sort(); + keys + } { + let mut encountered = HashSet::new(); + print_recursion(&state, &mut encountered, tag, 0); + } + } + } +} + +fn print_recursion(state: &State, encountered: &mut HashSet, tag: &str, level: u64) { + let mut out = String::new(); + for _ in 0..level { + out += " "; + } + out += "|-"; + out += tag; + println!("{out}"); + if encountered.contains(tag) { + return; + } + encountered.insert(tag.to_string()); + for tag in &state.tags[tag] { + print_recursion(state, encountered, tag, level + 1); + } +} diff --git a/src/commands/remove.rs b/src/commands/remove.rs new file mode 100644 index 0000000..72f6155 --- /dev/null +++ b/src/commands/remove.rs @@ -0,0 +1,13 @@ +use color_eyre::eyre::Result; +use tracing::error; + +use crate::{Error, State}; + +pub fn remove(state: &mut State, tag: &str, object: &str) -> Result<()> { + if state.tags.get(object).is_none() { + error!("No {object} in .state.json"); + return Err(Error::ObjectDoesntExist(object.to_string()).into()); + } + state.tags.get_mut(object).unwrap().remove(tag); + Ok(()) +} diff --git a/src/commands/tag.rs b/src/commands/tag.rs new file mode 100644 index 0000000..b60fa62 --- /dev/null +++ b/src/commands/tag.rs @@ -0,0 +1,19 @@ +use std::collections::HashSet; + +use tracing::{info, warn}; + +use crate::State; + +pub fn tag(state: &mut State, tag: &String, objects: &Vec) { + if state.tags.get(tag).is_none() { + info!("Adding {tag} to .state.json"); + state.tags.insert(tag.to_string(), HashSet::new()); + } + for object in objects { + if state.tags.get(object).is_none() { + warn!("No {object} in .state.json"); + continue; + } + state.tags.get_mut(object).unwrap().insert(tag.to_string()); + } +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs new file mode 100644 index 0000000..160e3d1 --- /dev/null +++ b/src/gui/mod.rs @@ -0,0 +1,7 @@ +use color_eyre::eyre::Result; + +use crate::cli::Cli; + +pub fn run(_flags: &Cli) -> Result<()> { + todo!() +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f831762 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,22 @@ +use clap::Parser; +use color_eyre::eyre::Result; + +mod cli; +mod commands; +mod traits; +mod types; +mod gui; + +use types::*; + +fn main() -> Result<()> { + color_eyre::install()?; + tracing_subscriber::fmt().init(); + + let flags = cli::Cli::parse(); + + match &flags.command { + Some(_) => cli::run(&flags), + None => gui::run(&flags), + } +} diff --git a/src/traits/mod.rs b/src/traits/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/traits/mod.rs @@ -0,0 +1 @@ + diff --git a/src/types/error.rs b/src/types/error.rs new file mode 100644 index 0000000..c750621 --- /dev/null +++ b/src/types/error.rs @@ -0,0 +1,11 @@ +#[derive(Debug, Clone)] +pub enum Error { + ObjectDoesntExist(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("{self:?}")) + } +} +impl std::error::Error for Error {} diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..71c1992 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,5 @@ +pub mod error; +pub mod state; + +pub use error::*; +pub use state::*; diff --git a/src/types/state.rs b/src/types/state.rs new file mode 100644 index 0000000..962c626 --- /dev/null +++ b/src/types/state.rs @@ -0,0 +1,60 @@ +use std::{ + collections::{HashMap, HashSet}, + path::PathBuf, +}; + +use color_eyre::eyre::Result; +use serde::{Deserialize, Serialize}; + +use crate::cli::Cli; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct State { + pub tags: HashMap>, +} + +impl State { + pub fn get_state(flags: &Cli) -> Result { + let statefile_location = match &flags.state { + Some(v) => v.to_owned(), + None => { + let mut current_dir = PathBuf::new(); + loop { + let mut testing_path = current_dir.clone(); + testing_path.push(".state.json"); + if testing_path.try_exists()? { + break; + } + current_dir.push(".."); + } + current_dir.push(".state.json"); + current_dir + } + }; + let state = std::fs::read_to_string(statefile_location).unwrap(); + let state: State = serde_json::from_str(&state)?; + Ok(state) + } + pub fn write_state(&self, flags: &Cli) -> Result<()> { + let state = match &flags.state { + Some(v) => v.to_owned(), + None => { + let mut current_dir = PathBuf::new(); + loop { + let mut testing_path = current_dir.clone(); + testing_path.push(".state.json"); + if testing_path.try_exists()? { + break; + } + current_dir.push(".."); + } + current_dir.push(".state.json"); + current_dir + } + }; + + std::fs::write(state, serde_json::to_string_pretty(self)?)?; + + Ok(()) + } +}