CLI done, moving onto the GUI
This commit is contained in:
commit
2fb92399ea
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
16
Cargo.toml
Normal file
16
Cargo.toml
Normal 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
14
shell.nix
Normal 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
47
src/cli.rs
Normal 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
27
src/commands/index.rs
Normal 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(¤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
|
||||
}
|
13
src/commands/init.rs
Normal file
13
src/commands/init.rs
Normal 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
11
src/commands/mod.rs
Normal 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
36
src/commands/print.rs
Normal 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
13
src/commands/remove.rs
Normal 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
19
src/commands/tag.rs
Normal 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
7
src/gui/mod.rs
Normal 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
22
src/main.rs
Normal 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
1
src/traits/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
11
src/types/error.rs
Normal file
11
src/types/error.rs
Normal 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
5
src/types/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
pub mod error;
|
||||
pub mod state;
|
||||
|
||||
pub use error::*;
|
||||
pub use state::*;
|
60
src/types/state.rs
Normal file
60
src/types/state.rs
Normal 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(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue