use std::path::PathBuf; use std::ops::Deref; use anyhow::Result; 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 lineread = readline.readline(&format!("{}> ", try_string(&state.tree.name()))); 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)); } } ["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)); } } } }