- Fixed CLI commands to prevent creating databases unnecessarily by using `StoreParams` with `create_if_not_exists: false` for `get`, `get_all`, `delete`, and `delete_all` commands. - Refactored `Store::sqlite_from_path` to accept `StoreParams` and dynamically generate SQLite open flags. - Added trace logging for received JSON content in `handle_write` command.
235 lines
6.4 KiB
Rust
235 lines
6.4 KiB
Rust
use clap::{Args, Parser, Subcommand};
|
|
use tracing::trace;
|
|
use std::io::{self, Read};
|
|
use std::path::PathBuf;
|
|
use std::process;
|
|
use vanth::hash as vanth_hash;
|
|
use vanth::{
|
|
ContentHash, Ty,
|
|
store::{Store, StoreParams},
|
|
};
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(name = "vanth")]
|
|
#[command(about = "Vanth CLI tool")]
|
|
pub struct Cli {
|
|
#[command(subcommand)]
|
|
pub command: Commands,
|
|
}
|
|
|
|
#[derive(Subcommand, Debug)]
|
|
pub enum Commands {
|
|
#[command(about = "Write a value to the store")]
|
|
Write(WriteArgs),
|
|
#[command(about = "Get a value from the store")]
|
|
Get(GetArgs),
|
|
#[command(about = "Get all values of a type from the store")]
|
|
GetAll(GetAllArgs),
|
|
#[command(about = "Delete a value by hash from all types")]
|
|
Delete(DeleteArgs),
|
|
#[command(about = "Delete all values of a type")]
|
|
DeleteAll(DeleteAllArgs),
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
pub struct WriteArgs {
|
|
#[arg(long, help = "Database file path")]
|
|
db: PathBuf,
|
|
#[arg(long, help = "Type name, e.g., path::to::Type")]
|
|
ty: String,
|
|
#[arg(long, help = "JSON value to write, optional (read from stdin if omitted)")]
|
|
value: Option<String>,
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
pub struct GetArgs {
|
|
#[arg(long, help = "Database file path")]
|
|
db: PathBuf,
|
|
#[arg(long, help = "Type name, e.g., path::to::Type")]
|
|
ty: String,
|
|
#[arg(help = "Content hash as 64-character hex string")]
|
|
content_hash: String,
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
pub struct GetAllArgs {
|
|
#[arg(long, help = "Database file path")]
|
|
db: PathBuf,
|
|
#[arg(long, help = "Type name, e.g., path::to::Type")]
|
|
ty: String,
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
pub struct DeleteArgs {
|
|
#[arg(long, help = "Database file path")]
|
|
db: PathBuf,
|
|
#[arg(help = "Content hash as 64-character hex string")]
|
|
content_hash: String,
|
|
}
|
|
|
|
#[derive(Args, Debug)]
|
|
pub struct DeleteAllArgs {
|
|
#[arg(long, help = "Database file path")]
|
|
db: PathBuf,
|
|
#[arg(long, help = "Type name, e.g., path::to::Type")]
|
|
ty: String,
|
|
}
|
|
|
|
pub fn execute(cli: Cli) {
|
|
match cli.command {
|
|
Commands::Write(args) => handle_write(&args),
|
|
Commands::Get(args) => handle_get(&args),
|
|
Commands::GetAll(args) => handle_get_all(&args),
|
|
Commands::Delete(args) => handle_delete(&args),
|
|
Commands::DeleteAll(args) => handle_delete_all(&args),
|
|
}
|
|
}
|
|
|
|
fn parse_ty(s: &str) -> Ty {
|
|
Ty {
|
|
path: s.split("::").map(|p| p.to_string()).collect(),
|
|
}
|
|
}
|
|
|
|
fn parse_hash(s: &str) -> ContentHash {
|
|
if s.len() != 64 {
|
|
eprintln!("Hash must be exactly 64 hexadecimal characters");
|
|
process::exit(1);
|
|
}
|
|
let mut hash = [0u8; 32];
|
|
for (i, byte) in hash.iter_mut().enumerate() {
|
|
let hex_slice = &s[i * 2..i * 2 + 2];
|
|
*byte = u8::from_str_radix(hex_slice, 16).unwrap_or_else(|_| {
|
|
eprintln!("Invalid hexadecimal in hash: {}", hex_slice);
|
|
process::exit(1);
|
|
});
|
|
}
|
|
ContentHash { hash }
|
|
}
|
|
|
|
fn handle_write(args: &WriteArgs) {
|
|
let mut store = Store::sqlite_from_path(args.db.clone(), StoreParams::default()).unwrap_or_else(|e| {
|
|
eprintln!("Error opening store: {:?}", e);
|
|
process::exit(1);
|
|
});
|
|
let ty = parse_ty(&args.ty);
|
|
|
|
let mut content = String::new();
|
|
if let Some(val) = &args.value {
|
|
content = val.clone();
|
|
} else {
|
|
io::stdin().read_to_string(&mut content).unwrap_or_else(|e| {
|
|
eprintln!("Error reading from stdin: {}", e);
|
|
process::exit(1);
|
|
});
|
|
}
|
|
|
|
trace!("Received JSON content: {}", content);
|
|
|
|
let value: serde_json::Value = serde_json::from_str(&content).unwrap_or_else(|e| {
|
|
eprintln!("Invalid JSON: {}", e);
|
|
process::exit(1);
|
|
});
|
|
let data = serde_json::to_vec(&value).unwrap();
|
|
let content_hash = vanth_hash(&value);
|
|
|
|
store.write_raw(ty, content_hash, data).unwrap_or_else(|e| {
|
|
eprintln!("Error writing to store: {:?}", e);
|
|
process::exit(1);
|
|
});
|
|
}
|
|
|
|
fn handle_get(args: &GetArgs) {
|
|
let mut store = Store::sqlite_from_path(
|
|
args.db.clone(),
|
|
StoreParams {
|
|
create_if_not_exists: false,
|
|
..Default::default()
|
|
},
|
|
)
|
|
.unwrap_or_else(|e| {
|
|
eprintln!("Error opening store: {:?}", e);
|
|
process::exit(1);
|
|
});
|
|
let ty = parse_ty(&args.ty);
|
|
let content_hash = parse_hash(&args.content_hash);
|
|
|
|
let raw = store.get_from_hash_raw(ty, content_hash).unwrap_or_else(|e| {
|
|
eprintln!("Error getting from store: {:?}", e);
|
|
process::exit(1);
|
|
});
|
|
match raw {
|
|
Some(data) => {
|
|
let output = String::from_utf8(data).unwrap_or_else(|e| {
|
|
eprintln!("Invalid UTF-8 in data: {}", e);
|
|
process::exit(1);
|
|
});
|
|
println!("{}", output);
|
|
}
|
|
None => {
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_get_all(args: &GetAllArgs) {
|
|
let mut store = Store::sqlite_from_path(
|
|
args.db.clone(),
|
|
StoreParams {
|
|
create_if_not_exists: false,
|
|
..Default::default()
|
|
},
|
|
)
|
|
.unwrap_or_else(|e| {
|
|
eprintln!("Error opening store: {:?}", e);
|
|
process::exit(1);
|
|
});
|
|
let ty = parse_ty(&args.ty);
|
|
|
|
let items = store.get_all_of_type_raw(ty).unwrap_or_else(|e| {
|
|
eprintln!("Error getting all from store: {:?}", e);
|
|
process::exit(1);
|
|
});
|
|
for (_, data) in items {
|
|
let output = String::from_utf8(data).unwrap_or_else(|e| {
|
|
eprintln!("Invalid UTF-8 in data: {}", e);
|
|
process::exit(1);
|
|
});
|
|
println!("{}", output);
|
|
}
|
|
}
|
|
|
|
fn handle_delete(args: &DeleteArgs) {
|
|
let mut store = Store::sqlite_from_path(
|
|
args.db.clone(),
|
|
StoreParams {
|
|
create_if_not_exists: false,
|
|
..Default::default()
|
|
},
|
|
)
|
|
.unwrap_or_else(|e| {
|
|
eprintln!("Error opening store: {:?}", e);
|
|
process::exit(1);
|
|
});
|
|
let content_hash = parse_hash(&args.content_hash);
|
|
}
|
|
|
|
fn handle_delete_all(args: &DeleteAllArgs) {
|
|
let mut store = Store::sqlite_from_path(
|
|
args.db.clone(),
|
|
StoreParams {
|
|
create_if_not_exists: false,
|
|
..Default::default()
|
|
},
|
|
)
|
|
.unwrap_or_else(|e| {
|
|
eprintln!("Error opening store: {:?}", e);
|
|
process::exit(1);
|
|
});
|
|
let ty = parse_ty(&args.ty);
|
|
|
|
store.delete_all_raw(ty).unwrap_or_else(|e| {
|
|
eprintln!("Error deleting all from store: {:?}", e);
|
|
process::exit(1);
|
|
});
|
|
}
|