CLI done, moving onto the GUI

This commit is contained in:
vanten-s 2024-05-05 13:35:12 +02:00
commit 2fb92399ea
Signed by: vanten-s
GPG key ID: DE3060396884D3F2
16 changed files with 304 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
Cargo.lock

16
Cargo.toml Normal file
View file

@ -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"

14
shell.nix Normal file
View file

@ -0,0 +1,14 @@
{ pkgs ? import <nixpkgs> {} }:
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/
'';
}

47
src/cli.rs Normal file
View file

@ -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<PathBuf>,
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(Debug, Clone, Subcommand, PartialEq, Eq)]
pub enum Commands {
Print { tag: Option<String> },
Tag { tag: String, objects: Vec<String> },
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(())
}

27
src/commands/index.rs Normal file
View file

@ -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<String> {
let mut paths = Vec::new();
let is_file = std::fs::metadata(&current_path).unwrap().is_file();
if is_file {
return vec![current_path.to_string_lossy().to_string()];
}
for path in std::fs::read_dir(&current_path).unwrap() {
paths.append(&mut index_recursion(state, path.unwrap().path()));
}
paths
}

13
src/commands/init.rs Normal file
View file

@ -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(())
}

11
src/commands/mod.rs Normal file
View file

@ -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::*;

36
src/commands/print.rs Normal file
View file

@ -0,0 +1,36 @@
use std::collections::HashSet;
use crate::State;
pub fn print(state: &State, tag: &Option<String>) {
match tag {
Some(tag) => print_recursion(&state, &mut HashSet::new(), &tag, 0),
None => {
for tag in {
let mut keys = state.tags.keys().collect::<Vec<_>>();
keys.sort();
keys
} {
let mut encountered = HashSet::new();
print_recursion(&state, &mut encountered, tag, 0);
}
}
}
}
fn print_recursion(state: &State, encountered: &mut HashSet<String>, 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);
}
}

13
src/commands/remove.rs Normal file
View file

@ -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(())
}

19
src/commands/tag.rs Normal file
View file

@ -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<String>) {
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());
}
}

7
src/gui/mod.rs Normal file
View file

@ -0,0 +1,7 @@
use color_eyre::eyre::Result;
use crate::cli::Cli;
pub fn run(_flags: &Cli) -> Result<()> {
todo!()
}

22
src/main.rs Normal file
View file

@ -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),
}
}

1
src/traits/mod.rs Normal file
View file

@ -0,0 +1 @@

11
src/types/error.rs Normal file
View file

@ -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 {}

5
src/types/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod error;
pub mod state;
pub use error::*;
pub use state::*;

60
src/types/state.rs Normal file
View file

@ -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<String, HashSet<String>>,
}
impl State {
pub fn get_state(flags: &Cli) -> Result<Self> {
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(())
}
}