use std::ops::Deref; use std::path::PathBuf; use anyhow::Result; use humansize::{file_size_opts, FileSize}; use rustyline::error::ReadlineError; use rustyline::Editor; use structopt::StructOpt; use sled::IVec; #[derive(StructOpt, Debug)] #[structopt(name = "sledcli")] struct Opt { /// Path to Sled database #[structopt(name = "path")] path: PathBuf, } enum DisplayMode { TryString, HexDump, Mixed, } struct State { db: sled::Db, tree: sled::Tree, displaymode: DisplayMode, } fn main() { let opt = Opt::from_args(); let db = sled::Config::default() .path(&opt.path) .open() .expect("Unable to open database"); let tree: sled::Tree = db.deref().clone(); let mut state = State { db, tree, displaymode: DisplayMode::Mixed, }; let mut readline = Editor::<()>::new(); loop { let prefix = match state.displaymode { DisplayMode::HexDump => "hex", DisplayMode::TryString => "str", DisplayMode::Mixed => "mix", }; let prompt = format!("[{}] {}> ", prefix, try_string(&state.tree.name())); let lineread = readline.readline(&prompt); match lineread { Ok(line) => { readline.add_history_entry(line.as_str()); if let Err(e) = do_command(&line, &mut state) { println!("Error: {}", e); } } Err(ReadlineError::Interrupted) => { println!("^C"); continue; } Err(ReadlineError::Eof) => break, Err(err) => { println!("Readline error: {:?}", err); break; } } } } fn try_string(input: &sled::IVec) -> String { let mut string = String::new(); utf8::LossyDecoder::new(|s| string.push_str(s)).feed(input); string } fn do_command(line: &str, state: &mut State) -> Result<()> { let parts = line .split(' ') .filter(|part| part.len() > 0) .collect::>(); if parts.is_empty() { return Ok(()); } match &parts[..] { ["ls"] => { let mut names = state.db.tree_names(); names.sort(); for name in names { println!("{}", try_string(&name)); } } ["ll"] => { let mut names = state.db.tree_names(); names.sort(); let mut total = 0; for name in names { let nent = state.db.open_tree(&name)?.len(); total += nent; println!("{:8} {}", nent, try_string(&name)); } println!("{:8} TOTAL", total); } ["lu"] => { let mut names = state.db.tree_names(); names.sort(); let mut total_nent = 0; let mut total_size = 0; for name in names { let tree = state.db.open_tree(&name)?; let nent = tree.len(); let mut size = 0; for ent in tree.iter() { let (k, v) = ent?; size += k.len() + v.len(); } total_nent += nent; total_size += size; println!( "{:8} {:>12} {}", nent, size.file_size(file_size_opts::CONVENTIONAL).unwrap(), try_string(&name) ); } println!("{:8} {:>12} TOTAL", total_nent, total_size.file_size(file_size_opts::CONVENTIONAL).unwrap()); } ["cd", treename] => { if state .db .tree_names() .iter() .any(|t| t == treename.as_bytes()) { state.tree = state.db.open_tree(treename.as_bytes())?; } else { println!("Tree {} does not exist", treename); } } ["hex"] => { state.displaymode = DisplayMode::HexDump; } ["str"] => { state.displaymode = DisplayMode::TryString; } ["mix"] => { state.displaymode = DisplayMode::Mixed; } ["keys"] => { for (i, pair) in state.tree.iter().enumerate() { if i >= 20 { println!("..."); break; } let (k, _v) = pair?; state.displaymode.print_key(&k); } } ["pairs"] => { for (i, pair) in state.tree.iter().enumerate() { if i >= 20 { println!("..."); break; } let (k, v) = pair?; state.displaymode.print_pair(&k, &v); } } bad_cmd => println!("Unrecognized command: {:?}", bad_cmd), } Ok(()) } impl DisplayMode { fn print_key(&self, k: &IVec) { match *self { DisplayMode::HexDump => { hexdump::hexdump(k); } _ => { println!("{}", try_string(k)); } } } fn print_pair(&self, k: &IVec, v: &IVec) { match *self { DisplayMode::HexDump => { hexdump::hexdump(k); hexdump::hexdump(v); println!(); } DisplayMode::Mixed => { println!("{}", try_string(k)); hexdump::hexdump(v); println!(); } DisplayMode::TryString => { println!("{}\t{}", try_string(k), try_string(v)); } } } }