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::<Vec<_>>();
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));
}
}
}
}