163 lines
3.3 KiB
Rust
163 lines
3.3 KiB
Rust
|
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));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|